擴展莫隊小總結(二) (回滾莫隊/二次離線莫隊)

上一篇:擴展莫隊小總結(一)

首先分析最普通的莫隊的時間複雜度:

$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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章