1. 程式人生 > >「CodePlus 2017 11 月賽」Yazid 的新生舞會(樹狀數組/線段樹)

「CodePlus 2017 11 月賽」Yazid 的新生舞會(樹狀數組/線段樹)

sizeof sum read 開頭 turn 單點 delta pac 圖片

  學習了新姿勢。。(一直看不懂大爺的代碼卡了好久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 的新生舞會(樹狀數組/線段樹)