學習了新姿勢。。(一直看不懂大爺的程式碼卡了好久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=;
int n, x, N, t;
int a[maxn], treety[maxn], pos[maxn], pre[maxn];
ll tree1[maxn], tree2[maxn], tree3[maxn], ans;
char buf[],*ptr=buf-;
inline int read()
{
char c=*++ptr;int s=,t=;
while(c<||c>)c=*++ptr;
while(c>=&&c<=){s=s*+c-'';c=*++ptr;}
return s*t;
}
inline void add(int x, int p)
{
x+=n+;
ll delta1=, delta2=-*x, delta3=1ll*x*x-*x+;
for(;x<=N;x+=x&-x)
{
if(treety[x]!=t) treety[x]=t, tree1[x]=tree2[x]=tree3[x]=;
tree1[x]+=delta1*p, tree2[x]+=delta2*p, tree3[x]+=delta3*p;
}
}
inline void query(int x, int ty)
{
x+=n+; 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(*s-r, ); add(*s-l+, -);}
int main()
{
fread(buf,,sizeof(buf),stdin); n=read(); x=read(); N=n+n+;
for(int i=;i<=n;i++) x=read(), pre[i]=pos[x], pos[x]=i;
for(int i=;i<n;i++)
if(pos[i])
{
int cnt=; ++t;
for(int j=pos[i];j;j=pre[j]) a[++cnt]=j;
update(, a[cnt]-, );
for(int j=cnt;j;j--)
{
query(*(cnt-j+)-a[j]-, );
query(*(cnt-j+)-((j-)?a[j-]+:n+), -);
update(a[j], (j-)?a[j-]-:n, cnt-j+);
}
}
printf("%lld\n", ans>>);
}