「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)

Posted Sakits

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)相关的知识,希望对你有一定的参考价值。

  学习了新姿势。。(一直看不懂大爷的代码卡了好久T T

  首先数字范围那么小可以考虑枚举众数来计算答案,设当前枚举到$x$,$s_i$为前$i$个数中$x$的出现次数,则满足$2*s_r-r > 2*s_l-l$的区间$[l+1,r]$其众数为$x$,这个显然可以用一个数据结构来维护。

  直接扫一遍效率是$O($数字种类数$*nlogn)$的,无法承受,但是我们发现,对于每一段非$x$的数,$2*s_i-i$是公差为$-1$的等差数列,所以它们对答案的贡献实际上可以一次性计算。设$L$为一段非$x$数的开头,$R$为结尾,则$\leq 2*s_R-R$的数贡献会被计算$len$次,$2*s_{R-1}-(R-1)$的数的贡献会被计算$len-1$次,...,$s_l-l$的数的贡献会被计算$1$次,这个贡献的计算次数也是个等差数列。

  那实际上我们有三种维护这个的方法。

  ①维护$a_i$表示$2*s_i-i$的出现次数,支持区间加和区间查询$\sum_{i=l}^r a_i*(i-l+1)$,较为麻烦,权值线段树。

  ②维护$s_i$表示$a_i$的前缀和,支持区间加一段等差数列和区间查询,挺可写,权值线段树。

  ③维护$s_i$的前缀和,支持区间加二次函数和单点查询,代码短但因为较抽象所以有些难调,树状数组,非常快。

  这里只说第三种写法,第一次见到这种操作...

  树状数组里实际上维护的是$s_1,s_1+s_2,s_1+s_2+s_3,...$,所以修改一段区间的时候,相当于给一段区间加上等差数列的求和,即$((1+i-l+1)*(i-l+1))/2=(i^2+(3-2l)i+l^2+3l+2)/2$,所以我们只要在树状数组上维护二次项,一次项和常数项,区间修改用差分,最后查询一段区间只要头尾相减就好了...

技术分享图片
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=1000010;
int n, x, N, t;
int a[maxn], treety[maxn], pos[maxn], pre[maxn];
ll tree1[maxn], tree2[maxn], tree3[maxn], ans;
char buf[20000010],*ptr=buf-1;
inline int read()
{
    char c=*++ptr;int s=0,t=1;
    while(c<48||c>57)c=*++ptr;
    while(c>=48&&c<=57){s=s*10+c-0;c=*++ptr;}
    return s*t;
}
inline void add(int x, int p)
{
    x+=n+1;
    ll delta1=1, delta2=3-2*x, delta3=1ll*x*x-3*x+2;
    for(;x<=N;x+=x&-x)
    { 
        if(treety[x]!=t) treety[x]=t, tree1[x]=tree2[x]=tree3[x]=0;
        tree1[x]+=delta1*p, tree2[x]+=delta2*p, tree3[x]+=delta3*p;
    }
}
inline void query(int x, int ty)
{
    x+=n+1; int pos=x;
    for(;x;x-=x&-x) if(treety[x]==t) 
    ans+=(tree1[x]*pos*pos+tree2[x]*pos+tree3[x])*ty;
}
inline void update(int l, int r, int s){add(2*s-r, 1); add(2*s-l+1, -1);}
int main()
{
    fread(buf,1,sizeof(buf),stdin); n=read(); x=read(); N=n+n+2;
    for(int i=1;i<=n;i++) x=read(), pre[i]=pos[x], pos[x]=i;
    for(int i=0;i<n;i++)
    if(pos[i])
    {
        int cnt=0; ++t;
        for(int j=pos[i];j;j=pre[j]) a[++cnt]=j;
        update(0, a[cnt]-1, 0);
        for(int j=cnt;j;j--) 
        {
            query(2*(cnt-j+1)-a[j]-1, 1);
            query(2*(cnt-j+1)-((j-1)?a[j-1]+1:n+2), -1);
            update(a[j], (j-1)?a[j-1]-1:n, cnt-j+1);
        }
    }
    printf("%lld\n", ans>>1);
}
View Code

 

以上是关于「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)的主要内容,如果未能解决你的问题,请参考以下文章

「CodePlus 2017 11 月赛」汀博尔 (二分答案)

[CodePlus 2017 11月赛&洛谷P4058]木材 题解(二分答案)

[LOJ 6249]「CodePlus 2017 11 月赛」汀博尔

「CodePlus 2017 11 月赛」大吉大利,晚上吃鸡!

「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)

「CodePlus 2017 12 月赛」白金元首与独舞