zoukankan      html  css  js  c++  java
  • Codeforces 1511G

    Codeforces 题面传送门 & 洛谷题面传送门

    一道名副其实的 hot tea

    首先显然可以发现这俩人在玩 Nim 游戏,因此对于一个 (c_iin[l,r]) 其 SG 值就是 (c_i-l),最终游戏的 SG 值就是 (oplus_{c_iin[l,r]}(c_i-l)),如果该值为 (0) 答案就是 B,否则答案是 A

    从这一步开始就有好几种不同复杂度的做法了,下面将一一介绍它们。

    下视 (n,m,q) 同阶。

    做法一:暴力

    迫真 · (2 imes 10^5)(n^2) 暴力踩过去。

    你看这 (2 imes 10^5) 的数据范围,你不写 (n^2) 写啥呢,难道还傻傻地想 (nlog^2n) 或是 (nlog n) 的做法?

    不要笑,现场 rk1 那位大哥就是暴力碾标算还抵过了 100 多发 hack。不得不说 CF 机子是真的快(

    就没啥好说的了,暴力从第一个 (ge l) 的位置开始枚举把它们异或起来即可。

    时间复杂度 (n^2)

    做法二:莫队+01-trie

    允许离线+静态区间问题,这无不启发我们往莫队的方向想。又因为此题涉及异或,因此考虑 01-trie,具体来说我们建立一棵 01-trie 维护当前莫队表示的区间中所有元素与 (l),那么向左/右移动右端点时 01-trie 时删除/插入元素,这个维护起来异常容易的不再赘述。向左/右移动左端点时需要先将 01-trie 中所有元素 (+1/-1) 再插入/删除新的元素,全局 (pm 1) 这个操作的维护方法算是经典套路了,考虑从低到高将序列中的元素插入 01-trie,那么全局 (+1) 相当于从根节点开始 DFS,每次 DFS 时交换当前节点两个儿子再 DFS 左儿子,如此进行下去,每次交换都实时更新答案即可。

    时间复杂度 (nsqrt{n}log n)

    做法三:分块+01-trie

    一个我自己 yy 出来的算法(

    上面莫队的做法每次向右移动都要插入新的元素,复杂度就总要多一个 log,并且莫队移动复杂度本身就带个根号导致这个 log 不太好摊,因此考虑将莫队换成分块。我们考虑设一个阈值 (B) 然后每 (B) 个元素一块,在下文中方便起见,设 (L_i,R_i) 分别表示每一块的左右端点。那么对于每一块 (i),我们设 (v_{i,j}) 表示这一块中所有 (c_k e 0) 的元素 (k)(k-j) 的异或和,其中 (j) 小于第 (i) 块的左端点,那么每次查询就整块调用预处理求得的值,散块暴力即可。那么怎么求 (v_{i,j}) 呢?我们考虑将这一块中所有 (c_k e 0)(k)(k-L_i+1) 插入 01-trie,然后从 (L_i-1) 开始往前遍历,每次进行全局 (+1) 操作,那么每次 (+1) 后得到的异或和就分别是我们要求的 (v_{i,L_i-1},v_{i,L_i-2},cdots)

    显然取 (B=sqrt{nlog n}) 时复杂度最优。

    时间复杂度 (nsqrt{nlog 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(nlog n)) 时复杂度最优。

    时间复杂度 (nsqrt{nlog n})

    有本事强制在线啊

    做法 499122181:根分+分块

    根据根号平衡的思想,我们把上面做法中 BIT 换成 (mathcal O(1)) 单点加,(mathcal O(sqrt{n})) 求异或和的分块即可实现 (nsqrt{n}) 的复杂度。

    u1s1 官方题解为啥没想到根号平衡啊,不太懂

    做法五:倍增

    这是一个 nb 至极的倍增做法,不仅可以强制在线,而且复杂度还是 (nlog n) 的 %%%%%%%%

    由于倍增本就基于二进制,而这题恰好涉及与下标有关的二进制运算,因此我们应尝试往倍增的方向考虑。我们设 (f_{i,j}=oplus_{c_kin[j,j+2^i-1]}(c_k-j))。首先考虑怎么求 (f_{i,j}):考虑首先拿 (f_{i-1,j}) 去异或 (f_{i-1,j+2^{i-1}}),那么显然这两个东西异或在一起恰好包含了 (2^0sim 2^{i-2}) 位的所有贡献,而显然 (2^{i-1}) 位会产生贡献,当且仅当有奇数个 (c_kin[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-1le r) 那么我们就将答案异或上 (f_{i,l}),即将 ([l,l+2^i-1]) 的贡献计入答案,同时对于 (c_kin[l+2^i,r]),它们也应对答案产生 (2^i) 的贡献,额外加上即可。

    时间复杂度 (nlog 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;
    }
    
  • 相关阅读:
    form表单重置、清空方法记录
    window location assign的使用
    简单易用的拾色器推荐
    display属性常用的四个值:block、inline、inline-block、none
    idea如何配置外部电脑访问本地项目
    Unity 2D相机公式换算(从其他博客上抄的)
    关于游戏逻辑模式的观点----谁调用谁
    Unity 角色场景传送功能
    Unity关于方法事件生命周期官方文档
    关于Unity物理事件的执行顺序的最新理解
  • 原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1511G.html
Copyright © 2011-2022 走看看