1. 程式人生 > 其它 >洛谷P4062 [Code+#1]Yazid 的新生舞會(樹狀陣列)

洛谷P4062 [Code+#1]Yazid 的新生舞會(樹狀陣列)

考慮數字x的貢獻

設S[i]表示x在前i個位置的出現次數

那麼如果存在區間[l+1,r]滿足要求,則S[r]-S[l]>(r-l)/2,即2S[r]-r>2S[l]-l

令T[i]=2S[i]-i

那可以得到一個n^2 * logn的做法:

列舉數字x,列舉位置,用一個數據結構統計一段區間內<某個數的個數

優化一下這個做法

對於某個數字x,看一下它的T

例如下例中,x=2

位置:0 1 2 3 4 5 6 7 8 9 10 11

數字: 1 2 5 3 2 3 1 2 4 2 1

T : 0 -1 0 -1 -2 -1 -2 -3 -2 -3 -2 -3

段: 1 1 2 2 2 3 3 3 4 4 5 5

我們發現如果存在k個x,那麼他把整個序列分為了k+1段,如上所示

每一段是一個等差數列

用a[i]表示T為i的數的出現次數

對於某個x的所有段,按從左到右依次處理

假設現在這一段的T值範圍為[l0,r0]

那麼以這一段為右端點的區間對答案的貢獻就是

式子裡j寫負無窮表示一個足夠小的邊界

然後對a中區間[l0,r0]的數加1

我們看用什麼樣的資料結構能夠快速完成查詢和修改的操作

查詢裡有2個∑,我們用字首和去掉一個,用b[i]表示a的字首和

每一段的貢獻就是,即區間求和

那麼修改操作對a中區間[l0,r0]的數加1,就是對b中區間[l0,r0]的數加首項為1,公差為1的等差數列

這可以用線段樹來完成

時間複雜度nlogn

繼續想,用c[i]表示b的字首和,即a的二階字首和

那麼每一段的貢獻就是c[r0-1] - c[l0-2]

區間加操作怎麼做?

區間加一個數可以想到利用差分陣列變為單點修改

令d表示a的差分陣列

現在a b c d的關係是 d是a的差分陣列,a是d的字首和,b是a的字首和即d的二階字首和,c是b的字首和即d的三階字首和

把所有的下標都右移n+1位,變為正整數

現在可以用樹狀陣列維護d[k] , k*d[k] , k*k*d[k] 的字首和

#include<bits/stdc++.h>

using namespace std;

#define N 500003

int nn;
long long c0[N<<1],c1[N<<1],c2[N<<1]; vector<int>v[N]; int a[N],sum[N]; #define lowbit(x) x&-x long long query(int x) { long long ss=0; for(int i=x;i;i-=lowbit(i)) ss+=1ll*(x+1)*(x+2)*c0[i]-(2ll*x+3)*c1[i]+c2[i]; return ss>>1; } void change(int x,int k) { for(int i=x;i<=nn;i+=lowbit(i)) { c0[i]+=k; c1[i]+=k*x; c2[i]+=1ll*k*x*x; } } int main() { int n,x; long long ans=0; scanf("%d%*d",&n); for(int i=1;i<=n;++i) { scanf("%d",&x); v[x].push_back(i); } int delta=n+1,y,siz,last; nn=2*n+1; for(int i=0;i<n;++i) { v[i].push_back(n+1); siz=v[i].size(); last=0; for(int j=0;j<siz;++j) { x=2*j-last+delta; y=2*j-(v[i][j]-1)+delta; ans+=query(x-1); if(y>2) ans-=query(y-2); change(y,1); change(x+1,-1); last=v[i][j]; } last=0; for(int j=0;j<siz;++j) { x=2*j-last+delta; y=2*j-(v[i][j]-1)+delta; change(y,-1); change(x+1,1); last=v[i][j]; } } printf("%lld\n",ans); }
作者:xxy 出處:http://www.cnblogs.com/TheRoadToTheGold/ 本文版權歸作者和部落格園共有,轉載請用連結,請勿原文轉載,Thanks♪(・ω・)ノ。