扩展莫队小总结(二) (回滚莫队/二次离线莫队)
首先分析最普通的莫队的时间复杂度:
$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