loading

AGC057C Increment or Xor

题意

给定一个长为 \(2^n\) 的序列 \(a\),保证为 \(0,1,\cdots,2^n-1\) 的排列,每次操作可以全局异或某个数或全局加一,问是否存在一种操作方案使得最终 \(a_i=i\)。

\(n\le 18\),操作次数限制 \(10^6\)。

分析

看到全局加一,我们知道全局加一有一个用 01trie 维护的做法,即从低到高位建 01trie 然后将极右侧的链上所有点翻转,翻转一个点代表交换它的左右儿子,本题中忽略没有左右儿子的点,叶子的定义改为忽略掉这些点的叶子。对于本题,对每个 \(a_i\) 维护值为它的下标是多少,则我们希望 \(a_i\) 处的值为 \(i\)。区间 +1 前面说过了相当于翻转极右链,区间 xor 一个 \(2^i\) 就相当于将一层中所有点翻转。现在所有操作都变成了翻转某些点,由此衍生出一个有解必要条件:\(\lvert a_i-a_{i+m}\rvert=m,m=2^{n-1}\)。因为 \(i\) 和 \(i+m\) 是叶子的两个儿子,它们两个不管怎么翻转都必然都是叶子的两个儿子,因为最终想让它们差为 \(m\),所以最开始它们必须差为 \(m\)。虽然还能推出更强的必要条件,但我们还不需要它们。

虽然 +1 操作只能对极右链进行,但实际上我们可以通过一次异或让我们想操作的链移到极右侧(即异或上 \((2^n-1)\oplus x\))。注意到所有非叶子的子树大小都是偶数,所以无论叶子层是否操作,都不会改变非叶子的状态。所以钦定叶子层不操作,然后模拟进行将那些必须要操作的叶子(即所有 \(a_i\ge m\) 的 \(i\),\(x\) 即为 \(a_i\);注意虽然叶子在 trie 上只能表示 \([0,m)\) 的值,但是你发现由于 \(x\ge m\),所以和 \(2^n-1\) 异或后最高位没贡献了)操作,实现上只需要枚举 \(i=0\sim m-1\) 看看是否 \(\ge m\) 即可。最终得到一个数列 \(x\),此时我们只能执行全局异或,只需要看看异或上 \(a_0\) 是否合法就完了。为了查询一个下标的值,需要维护一个 01trie 支持全局 +1 全局 xor 单点查值,对于单点查值只需要不断往上爬父亲。操作次数 \(\le 2^n+1\),时间复杂度 \(O(2^nn)\)。

const int maxn=1<<18;
int n,a[maxn],pos[maxn];
vector<int>vec;
int tag;
int nxt[maxn<<1][2],fa[maxn<<1],cnt=1;
int ins(int x){
	int p=1;
	rep(i,0,n-1){
		int q=(x>>i)&1;
		if(!nxt[p][q])nxt[p][q]=++cnt,fa[cnt]=p;
		p=nxt[p][q];
	}
	return p;
}
void upd(){
	int p=1;
	rep(i,0,n-1){
		swap(nxt[p][0],nxt[p][1]);
		p=nxt[p][(tag>>i)&1];
	}
}
int qry(int x){
	int p=pos[x],res=0;
	per(i,n-1,0){
		if(nxt[fa[p]][((tag>>i)&1)^1]==p)res|=1<<i;
		p=fa[p];
	}
	return res;
}
inline void solve_the_problem(){
	n=rd();
	rep(i,0,(1<<n)-1)a[i]=rd();
	const int m=1<<(n-1);
	rep(i,0,m-1)if(a[i]%m!=a[i+m]%m)return (void)PN;
	rep(i,0,(1<<n)-1)pos[i]=ins(a[i]);
	rep(i,0,m-1){
		int x=qry(i);
		if(x>=m){
			vec.pb(((1<<n)-1)^x),vec.pb(-1);
			tag^=((1<<n)-1)^x,upd();
		}
	}
	rep(i,0,(1<<n)-1)a[i]=qry(i);
	int res=a[0];
	vec.pb(res);
	rep(i,0,(1<<n)-1)a[i]^=res;
	rep(i,0,(1<<n)-1)if(a[i]!=i)return (void)PN;
	PY;write(vec.size());
	for(int i:vec)write(i,32);
}
posted @ 2026-01-21 20:28  dcytrl  阅读(19)  评论(0)    收藏  举报