首先分析最普通的莫隊的時間複雜度:
$Q$次詢問,每次詢問移動左右指針,保證移動的長度爲$O(\sqrt {n})$級別,每次添加或刪除操作的時間都是$O(k)$
總時間複雜度$O(Qk\sqrt{n})$
回滾莫隊:
普通莫隊要求刪除和添加都是$O(k)$的級別,但有些情況下添加或刪除操作並不能較快地修改答案,例如以下題目:
題目大意:給你一個長度爲n的序列,Q次詢問,每次詢問[l,r]範圍內相同的數相距的最遠距離
維護兩個最值桶,分別表示當前區間$[l,r]$內,數i出現的位置的最小值和最大值
考慮普通莫隊的拓展過程
更改一個位置,先要更新桶,再更新全局答案
添加一個位置,比如$[l,r]$變爲$[l,r+1]$更新答案很容易:先用$a[r+1]$更新桶,再看是否能更新全局的答案
刪除一個位置,如$[l,r]$變爲$[l,r-1]$呢?桶可以通過鏈表記錄位置或用棧記錄操作來更新;但全局答案可能減小!次大的答案並不能在$O(1)$時間內找到
想個辦法讓莫隊只有添加操作!
我們依次枚舉莫隊左右指針所在塊的標號$[lx,rx]$
我們先記錄$[lx+1,rx-1]$這些塊的答案
對於在$[lx,rx]$內的所有詢問,每次都從$lx+1$的開頭向左擴展,再從$rx-1$的末尾向右擴展,得到該詢問的答案。
再回退答案,並回退這次拓展操作對桶的貢獻(這裏表明回退桶的時間也必須是$O(k)$的,與添加一個位置同時間複雜度)
接着拓展$rx$,也就是$[lx,rx]$變爲$[lx,rx+1]$時,把$[lx-1,rx-1]$的答案拓展到$[lx-1,rx]$就行了
最外層向右移動$lx$,我們直接回退所有操作,重新從lx開始記錄答案
1 #include <cmath> 2 #include <queue> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #define ll long long 7 using namespace std; 8 const int maxn=400000, N1=400005; const int inf=0x3f3f3f3f; 9 10 template <typename _T> void read(_T &ret) 11 { 12 ret=0; _T fh=1; char c=getchar(); 13 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 14 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 15 ret=ret*fh; 16 } 17 18 int n,Q,sq=500,m,de; 19 int tval[N1],di[N1],a[N1],b[N1]; 20 int id[N1],st[N1],ed[N1]; 21 22 struct QUES{ 23 int l,r,ans,id; 24 }qu[N1]; 25 int cmp1(QUES s1,QUES s2) 26 { 27 if(id[s1.l]!=id[s2.l]) return id[s1.l]<id[s2.l]; 28 return id[s1.r]<id[s2.r]; 29 } 30 int cmp2(QUES s1,QUES s2) 31 { 32 return s1.id<s2.id; 33 } 34 35 int ma,mcnt; 36 struct PRE{int id,pmi,pma; }op[N1]; int cnt; 37 int ami[N1],ama[N1]; 38 39 void add(int i) 40 { 41 cnt++; op[cnt].id=i; op[cnt].pmi=ami[b[i]], op[cnt].pma=ama[b[i]]; 42 ami[b[i]]=min(ami[b[i]],i); ama[b[i]]=max(ama[b[i]],i); 43 ma=max(ma,ama[b[i]]-ami[b[i]]); 44 mcnt=max(mcnt,cnt); 45 } 46 void del(int i) 47 { 48 if(i!=op[cnt].id) puts("-1"); //if(!cnt){ puts("-1"); return; } 49 ami[b[i]]=op[cnt].pmi, ama[b[i]]=op[cnt].pma; cnt--; 50 mcnt=max(mcnt,cnt); 51 } 52 53 int main() 54 { 55 // freopen("t.in","r",stdin); 56 // freopen("t.txt","r",stdin); 57 // clock_t ts,te; ts=clock(); 58 scanf("%d",&n); 59 for(int i=1;i<=n;i++) scanf("%d",&a[i]), tval[i]=a[i], id[i]=(i-1)/sq+1; 60 for(int i=1;i<=id[n];i++) st[i]=(i-1)*sq+1, ed[i]=min(i*sq,n); 61 scanf("%d",&Q); 62 for(int q=1;q<=Q;q++) 63 { 64 scanf("%d%d",&qu[q].l,&qu[q].r); 65 // read(qu[q].l), read(qu[q].r); 66 qu[q].id=q; 67 } 68 sort(tval+1,tval+n+1); 69 for(int i=1;i<=n;i++) 70 if(tval[i]!=tval[i-1]) di[++m]=tval[i]; 71 di[m+1]=inf; 72 for(int i=1;i<=n;i++) b[i]=lower_bound(di+1,di+m+1,a[i])-di; 73 for(int i=1;i<=m;i++) ami[i]=n+1, ama[i]=0; 74 75 // for(int i=1;i<=n;i++) printf("%d\n",b[i]); 76 sort(qu+1,qu+Q+1,cmp1); 77 int i=1,tmp,l,r; 78 for(int al=1;al<=id[n];al++) 79 { 80 for(;i<=Q&&id[qu[i].l]==al&&id[qu[i].r]==al;i++) 81 { 82 ma=0; 83 for(int j=qu[i].l;j<=qu[i].r;j++) add(j); 84 qu[i].ans=ma; 85 for(int j=qu[i].r;j>=qu[i].l;j--) del(j); 86 } 87 int nl=ed[al]+1,nr=ed[al]; ma=0; 88 for(int ar=al+1;ar<=id[n];ar++) 89 { 90 for(;i<=Q&&id[qu[i].l]==al&&id[qu[i].r]==ar;i++) 91 { 92 tmp=ma; 93 for(l=ed[al];l>=qu[i].l;l--) add(l); 94 for(r=st[ar];r<=qu[i].r;r++) add(r); 95 qu[i].ans=ma; 96 for(r=qu[i].r;r>=st[ar];r--) del(r); 97 for(l=qu[i].l;l<=ed[al];l++) del(l); 98 ma=tmp; 99 } 100 for(;nr+1<=ed[ar];nr++) add(nr+1); 101 } 102 for(;nr>=nl;nr--) del(nr); 103 if(cnt) puts("-1"); 104 } 105 sort(qu+1,qu+Q+1,cmp2); 106 for(int q=1;q<=Q;q++) 107 { 108 printf("%d\n",qu[q].ans); 109 } 110 return 0; 111 }
二次離線莫隊:
題目大意:給你一個長度爲n的序列a,再給定數字K,以及Q次詢問, 每次詢問$l\le i \le j \le r$中,$a[i]\ xor \ a[j] $有K個1的i,j有多少對。保證$a[i]\le 2^{14}$
試試普通莫隊:
每次操作怎麼搞?
挪一下項,$a[i]\ xor \ a[j] = K個1$,變爲$a[i]\ xor \ K個1 = a[j]$
對當前區間$[l,r]$,用桶記錄$a[i]\ xor \ K個1$的個數
每次添加/刪除操作先去掉這個位置的貢獻,再更新桶。更新桶的最差時間複雜度是$O(C_{14}^{7})$
總時間$O(C_{14}^{7}n\sqrt {n})$
這下啥莫隊也不行了
於是lxl神犇開發出了奇妙的二次離線莫隊
以向右拓展爲例:現在我們要從$[l,r]$更新成$[l,r+x]$
設$f(x,[l,r])$表示$x$放入$[l,r]$的桶裏能得到的答案
總貢獻是$\sum_{i=r+1}^{r+x}f(i,[l,i-1])=f(i,[1,i-1])-f(i,[1,l-1])$
這個貢獻是可以差分的!這是二次離線莫隊能用的必要條件
$\sum_{i=r+1}^{r+x}f(i,[1,i-1])-\sum_{i=r+1}^{r+x}f(i,[1,l-1])$
前面這一項可以在$O(C_{14}^{7}n)$的時間和$O(n)$的空間預處理,莫隊的過程中再$O(1)$取出
後面這一項呢?我們把$[r+1,r+x]$這一段詢問用$vector$掛在$l-1$上
從左到右枚舉$l$,更新桶,再暴力處理所有掛在$l$上的詢問的答案。
向左拓展和向右拓展的處理方式類似,刪除和添加也很類似
總時間$O(kn+n\sqrt{n})$
1 #include <cmath> 2 #include <vector> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #define ll long long 7 using namespace std; 8 const int maxn=2e5, N1=maxn+5; 9 10 template <typename _T> void read(_T &ret) 11 { 12 ret=0; _T fh=1; char c=getchar(); 13 while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); } 14 while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); } 15 ret=ret*fh; 16 } 17 18 int n,Q,K,sq,m; 19 int a[N1],bin[N1],popc[N1],id[N1]; 20 ll delta[N1],cnt[N1]; 21 22 struct QUES{ int l,r,id; ll ans; }qu[N1]; 23 int cmp1(QUES s1,QUES s2) 24 { 25 if(id[s1.l]!=id[s2.l]) return id[s1.l]<id[s2.l]; 26 return id[s1.r]<id[s2.r]; 27 } 28 int cmp2(QUES s1,QUES s2){ return s1.id<s2.id; } 29 vector<QUES>lm[N1],rm[N1]; 30 ll ld[N1],rd[N1]; 31 32 void getmove() 33 { 34 int nl=1,nr=1; ll tans; QUES t; 35 for(int q=1;q<=Q;q++) 36 { 37 if(nr<qu[q].r){ lm[nl-1].push_back((QUES){nr+1,qu[q].r,q,1}); nr=qu[q].r; } 38 if(nl>qu[q].l){ rm[nr+1].push_back((QUES){qu[q].l,nl-1,q,1}); nl=qu[q].l; } 39 if(nl<qu[q].l){ rm[nr+1].push_back((QUES){nl,qu[q].l-1,q,-1}); nl=qu[q].l; } 40 if(nr>qu[q].r){ lm[nl-1].push_back((QUES){qu[q].r+1,nr,q,-1}); nr=qu[q].r; } 41 } 42 for(int l=1;l<=n;l++) 43 { 44 ld[l]+=cnt[a[l]]; //前一項 45 for(int i=1;i<=m;i++) cnt[a[l]^bin[i]]++; 46 for(int j=0;j<lm[l].size();j++) 47 { 48 t=lm[l][j]; tans=0; 49 for(int i=t.l;i<=t.r;i++) tans+=cnt[a[i]]; 50 delta[lm[l][j].id]+= tans*lm[l][j].ans*(-1); 51 } 52 } 53 memset(cnt,0,sizeof(cnt)); 54 for(int r=n;r>=1;r--) 55 { 56 rd[r]+=cnt[a[r]]; //前一項 57 for(int i=1;i<=m;i++) cnt[a[r]^bin[i]]++; 58 for(int j=0;j<rm[r].size();j++) 59 { 60 t=rm[r][j]; tans=0; 61 for(int i=t.l;i<=t.r;i++) tans+=cnt[a[i]]; 62 delta[rm[r][j].id]+= tans*rm[r][j].ans*(-1); 63 } 64 } 65 } 66 void solve() 67 { 68 int nl=1,nr=1; ll ans=0; QUES t; 69 for(int q=1;q<=Q;q++) 70 { 71 ans+=delta[q]; 72 if(nr<qu[q].r) for(;nr+1<=qu[q].r;nr++) ans+=ld[nr+1]; 73 if(nl>qu[q].l) for(;nl-1>=qu[q].l;nl--) ans+=rd[nl-1]; 74 if(nl<qu[q].l) for(;nl+1<=qu[q].l;nl++) ans-=rd[nl]; 75 if(nr>qu[q].r) for(;nr-1>=qu[q].r;nr--) ans-=ld[nr]; 76 qu[q].ans=ans; 77 } 78 } 79 80 int main() 81 { 82 freopen("a.in","r",stdin); 83 scanf("%d%d%d",&n,&Q,&K); sq=sqrt(n); 84 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 85 for(int i=1;i<=n;i++) id[i]=(i-1)/sq+1; 86 for(int i=0;i<(1<<14);i++) 87 { 88 popc[i]=popc[i>>1]+(i&1); 89 if(popc[i]==K) bin[++m]=i; 90 } 91 for(int q=1;q<=Q;q++) 92 { 93 read(qu[q].l); read(qu[q].r); 94 qu[q].id=q; 95 } 96 sort(qu+1,qu+Q+1,cmp1); 97 getmove(); 98 solve(); 99 sort(qu+1,qu+Q+1,cmp2); 100 for(int q=1;q<=Q;q++) printf("%lld\n",qu[q].ans); 101 return 0; 102 } 103