Codeforces 1511G - Chips on a Board(01trie/倍增)
一道名副其實的 hot tea
首先顯然可以發現這倆人在玩 Nim 遊戲,因此對於一個 \(c_i\in[l,r]\) 其 SG 值就是 \(c_i-l\),最終遊戲的 SG 值就是 \(\oplus_{c_i\in[l,r]}(c_i-l)\),如果該值為 \(0\) 答案就是 B
,否則答案是 A
。
從這一步開始就有好幾種不同複雜度的做法了,下面將一一介紹它們。
下視 \(n,m,q\) 同階。
做法一:暴力
迫真 · \(2\times 10^5\) 給 \(n^2\) 暴力踩過去。
你看這 \(2\times 10^5\) 的資料範圍,你不寫 \(n^2\)
不要笑,現場 rk1 那位大哥就是暴力碾標算還抵過了 100 多發 hack。不得不說 CF 機子是真的快(
就沒啥好說的了,暴力從第一個 \(\ge l\) 的位置開始列舉把它們異或起來即可。
時間複雜度 \(n^2\)。
做法二:莫隊+01-trie
允許離線+靜態區間問題,這無不啟發我們往莫隊的方向想。又因為此題涉及異或,因此考慮 01-trie,具體來說我們建立一棵 01-trie 維護當前莫隊表示的區間中所有元素與 \(l\),那麼向左/右移動右端點時 01-trie 時刪除/插入元素,這個維護起來異常容易的不再贅述。向左/右移動左端點時需要先將 01-trie 中所有元素 \(+1/-1\)
時間複雜度 \(n\sqrt{n}\log n\)
做法三:分塊+01-trie
一個我自己 yy 出來的演算法(
上面莫隊的做法每次向右移動都要插入新的元素,複雜度就總要多一個 log,並且莫隊移動複雜度本身就帶個根號導致這個 log 不太好攤,因此考慮將莫隊換成分塊。我們考慮設一個閾值 \(B\) 然後每 \(B\)
顯然取 \(B=\sqrt{n\log n}\) 時複雜度最優。
時間複雜度 \(n\sqrt{n\log n}\)
const int MAXN=2e5;
const int BLK=300;
const int MAXP=1e5;
const int LOG_N=20;
int n,m,qu,cnt[MAXN+5],v[BLK+5][MAXN+5];
int bel[MAXN+5],L[BLK+5],R[BLK+5];
int ch[MAXP+5][2],siz[MAXP+5],ncnt=0,rt=0,val[MAXP+5];
void clear(){
memset(ch,0,sizeof(ch));ncnt=rt=0;
memset(siz,0,sizeof(siz));memset(val,0,sizeof(val));
}
void pushup(int k,int d){val[k]=val[ch[k][0]]^val[ch[k][1]]^((siz[ch[k][1]]&1)?(1<<d):0);}
void insert(int &k,int v,int d){
if(!k) k=++ncnt;siz[k]++;if(d==LOG_N) return;
insert(ch[k][v>>d&1],v,d+1);pushup(k,d);
}
void add1(int k,int d){
if(!k) return;swap(ch[k][0],ch[k][1]);
add1(ch[k][0],d+1);pushup(k,d);
}
int query(int l,int r){
if(bel[l]==bel[r]){
int res=0;
for(int i=l;i<=r;i++) if(cnt[i]) res^=(i-l);
return res;
} else {
int res=0;
for(int i=l;i<=R[bel[l]];i++) if(cnt[i]) res^=(i-l);
for(int i=L[bel[r]];i<=r;i++) if(cnt[i]) res^=(i-l);
for(int i=bel[l]+1;i<bel[r];i++) res^=v[i][l];
return res;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++) scanf("%d",&x),cnt[x]^=1;
int blk_sz=max((int)sqrt(m*log2(m)),1),blk_cnt=(m-1)/blk_sz+1;
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,m);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
clear();
for(int j=L[i];j<=R[i];j++) if(cnt[j]) insert(rt,j-L[i],0);
for(int j=L[i]-1;j;j--) add1(rt,0),v[i][j]=val[rt];
} int qu;scanf("%d",&qu);
while(qu--){
int l,r;scanf("%d%d",&l,&r);
printf("%s",(query(l,r))?"A":"B");
}
return 0;
}
做法四:根分+BIT
這是官方題解的做法(
考慮設一個閾值 \(K\) 並將詢問離線下來。那麼我們將所有位分為 \(<2^K\) 的位和 \(\ge 2^K\) 的位,對於 \(<2^K\) 的位就暴力存下所有數對 \(2^K\) 取模的值,查詢時用個桶維護一下即可。對於 \(\ge 2^K\) 的位我們考慮掃描線,將詢問掛在左端點處,可以注意到隨著左端點的增大,每個 \(c_i-l\) 的 \(\ge 2^K\) 的位最多變化 \(\dfrac{m}{2^K}\) 次。我們就用 BIT 維護這些 \(c_i-l\) 的 \(\ge 2^K\) 位的異或值即可。取 \(K=\log_2(n\log n)\) 時複雜度最優。
時間複雜度 \(n\sqrt{n\log n}\)
有本事強制線上啊
做法 499122181:根分+分塊
根據根號平衡的思想,我們把上面做法中 BIT 換成 \(\mathcal O(1)\) 單點加,\(\mathcal O(\sqrt{n})\) 求異或和的分塊即可實現 \(n\sqrt{n}\) 的複雜度。
u1s1 官方題解為啥沒想到根號平衡啊,不太懂
做法五:倍增
這是一個 nb 至極的倍增做法,不僅可以強制線上,而且複雜度還是 \(n\log n\) 的 %%%%%%%%
由於倍增本就基於二進位制,而這題恰好涉及與下標有關的二進位制運算,因此我們應嘗試往倍增的方向考慮。我們設 \(f_{i,j}=\oplus_{c_k\in[j,j+2^i-1]}(c_k-j)\)。首先考慮怎麼求 \(f_{i,j}\):考慮首先拿 \(f_{i-1,j}\) 去異或 \(f_{i-1,j+2^{i-1}}\),那麼顯然這兩個東西異或在一起恰好包含了 \(2^0\sim 2^{i-2}\) 位的所有貢獻,而顯然 \(2^{i-1}\) 位會產生貢獻,當且僅當有奇數個 \(c_k\in[j+2^{i-1},j+2^i)\),因此我們可以得到以下轉移方程:
f[i][j]=f[i-1][j]^f[i-1][j+(1<<i-1)]^(((a[j+(1<<i)-1]-a[j+(1<<i-1)-1])&1)<<i-1);
其中 \(a\) 為桶的字首和。
得出了 \(f\) 陣列之後求解答案就非常 easy 了,考慮從大到小遍歷每一位 \(i\),如果 \(l+2^i-1\le r\) 那麼我們就將答案異或上 \(f_{i,l}\),即將 \([l,l+2^i-1]\) 的貢獻計入答案,同時對於 \(c_k\in[l+2^i,r]\),它們也應對答案產生 \(2^i\) 的貢獻,額外加上即可。
時間複雜度 \(n\log n\)。
const int LOG_N=18;
const int MAXN=2e5;
int n,m,qu,a[MAXN+5],f[LOG_N+2][MAXN+5];
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++) scanf("%d",&x),a[x]++;
for(int i=1;i<=m;i++) a[i]+=a[i-1];
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=m;j++)
f[i][j]=f[i-1][j]^f[i-1][j+(1<<i-1)]^(((a[j+(1<<i)-1]-a[j+(1<<i-1)-1])&1)<<i-1);
scanf("%d",&qu);while(qu--){
int l,r,cur,res=0;scanf("%d%d",&l,&r);cur=l;
for(int i=LOG_N;~i;i--){
if(r-cur+1>=(1<<i)) res^=f[i][cur],cur+=(1<<i),res^=((a[r]-a[cur-1])&1)<<i;
} printf("%s",(res)?"A":"B");
}
return 0;
}