学习了新姿势。。(一直看不懂大爷的代码卡了好久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); }