扩展莫队小总结(二) (回滚莫队/二次离线莫队)

上一篇:扩展莫队小总结(一)

首先分析最普通的莫队的时间复杂度:

$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 }
View Code

 

二次离线莫队:

题目传送门:第十四分块(前体)

题目大意:给你一个长度为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         
View Code

 

posted @ 2021-05-10 19:31  guapisolo  阅读(108)  评论(0编辑  收藏  举报