[整理]莫隊二次離線
阿新 • • 發佈:2021-06-25
莫隊二次離線用於處理移動區間端點時複雜度過大的問題。
以擴充套件區間 \([l,r]\) 到 \([l,r+1]\) 為例,設多出的貢獻為 \(f(r+1,[l,r])\),這個貢獻必須是可以差分成 \(f(r+1,[1,r])-f(r+1,[1,l-1])\) 形式的。
到這裡你當然可以把它們直接全部離線下來算貢獻,但是你的空間複雜度會比較爆炸。讓我們再來仔細觀察這個東西,減號前面的部分可以簡簡單單預處理,後面的東西貢獻的區間都是固定的,我們再把它離線一次。
和普通莫隊一樣需要分情況討論,這裡以從 \([l,r]\) 移動到 \([l,R]\)(其中 \(R\) 為查詢區間端點)為例,我們計算出減號前的貢獻,再在字首 \(l-1\)
if(R>r)q1[l-1].pub({r+1,R,i});//把詢問離線到相應的位置
while(R>r)q[i].ans+=pre[++r];//順便計算減號前貢獻
對於其他幾種情況是同理的,需要注意符號。
然後我們發現,在處理左端點移動時,會出現形如 \(f(l,[1,l])\) 的東西,我們沒有處理過它,於是可以假裝它是我們處理過的 \(f(l,[1,l-1])\)。而只有 \(k=0\) 時相等的元素會有影響,所以特判一下即可。
模板題核心程式碼(預處理那裡運用了異或的一些性質):
const int N=200010; int n,m,pp,a[N],bel[N],len,ans[N]; struct Query { int l,r,idx,ans; }q[N]; bool operator < (Query A,Query B){ return bel[A.l]==bel[B.l]?A.r<B.r:bel[A.l]<bel[B.l]; } struct Query1 { int l,r,idx,sgn; }; vector<Query1> q1[N]; vector<int> buc; int b[N],pre[N]; signed main(){ Read(n),Read(m),Read(pp),len=sqrt(n); for(rg int i=1;i<=n;i++)Read(a[i]); for(rg int i=1;i<=m;i++)Read(q[i].l),Read(q[i].r),q[i].idx=i; for(rg int i=1;i<=n;i++)bel[i]=(i-1)/len+1; sort(q+1,q+1+m); for(rg int i=0;i<=16384;i++){ if(__builtin_popcount(i)==pp)buc.pub(i); } for(rg int i=1;i<=n;i++){ pre[i]=b[a[i]]; for(rg auto j:buc)b[a[i]^j]++; } for(rg int i=1,l=1,r=0;i<=m;i++){ int L=q[i].l,R=q[i].r; if(L<l)q1[r].pub({L,l-1,i,1}); while(L<l)q[i].ans-=pre[--l]; if(L>l)q1[r].pub({l,L-1,i,-1}); while(L>l)q[i].ans+=pre[l++]; if(R>r)q1[l-1].pub({r+1,R,i,-1}); while(R>r)q[i].ans+=pre[++r]; if(R<r)q1[l-1].pub({R+1,r,i,1}); while(R<r)q[i].ans-=pre[r--]; } memset(b,0,sizeof(b)); for(rg int i=1;i<=n;i++){ for(rg auto j:buc)b[a[i]^j]++; for(rg auto j:q1[i]){ for(rg int k=j.l;k<=j.r;k++){ q[j.idx].ans+=j.sgn*(b[a[k]]-(k<=i&&!pp)); } } } for(rg int i=1;i<=m;i++)q[i].ans+=q[i-1].ans; for(rg int i=1;i<=m;i++)ans[q[i].idx]=q[i].ans; for(rg int i=1;i<=m;i++)cout<<ans[i]<<endl; KafuuChino HotoKokoa }