选数 题解

选数 题解

首先,设最初取值为\(x\),按照套路,我们设异或前缀和:\(pre_i=a_1\oplus a_2…\oplus a_i\),设\(f(x)=\left(\left\lfloor\frac{2x}{2^n}\right\rfloor+2x\right)\bmod 2^n\)

注意到:\(0\le a_i<2^n\),也即其二进制下不会超过\(n\)位。

那么我们实际上要求的即为:\(\max_{i=0}^m f(x\oplus pre_i)\oplus pre_n\oplus pre_i\)

简单带入一下\(f\),容易得到:

\(f(x\oplus pre_i)=f(x)\oplus f(pre_i)\)

有趣的是,\(f(x)\)相当于是将\(x\)在二进制下向左做一个一个单位长度的循环

这也可以证明我们对下列所有的\(f\)的变换是正确的,每一个\(f(x)\)都有唯一的值与之对应。

所以式子变为:\(f(x)\oplus f(pre_i)\oplus pre_n\oplus pre_i\),其中后半截的可以预处理的,不妨设\(s_i=f(pre_i)\oplus pre_n\oplus pre_i\)

\(s_1\sim s_n\)插入trie,并且注意到可以在不异或任何值的情况下对\(x\)进行变化,所以需要插入0别问90pts是为什么

再者,再看这个式子:\(\max_{i=0}^m f(x)\oplus s_i\),注意到\(x\in [0,2^n-1],f(x)\in[0,2^n-1]\),所以可以将\(f(x)\)\(x\)替换掉。

这样的话,式子化为\(\max_{i=0}^m x\oplus s_i\),我们的问题就变成了如何求得这个最大值。

我们设solve(p,now,ans)表示当前遍历到trie的指针\(p\),已经计算到第\(now\)位,答案最大为\(ans\)

此时我们来分类讨论,毕竟先后手都足够聪明。

  1. \(p\)有两个儿子,则不论第\(now\)位填什么,都有决策使这个位变为0,所以递归solve(t[p][0],now-1,ans<<1),slove(t[p][1],now-1,ans<<1)
  2. \(p\)仅有一个儿子,则可以反着填数,使这一位变成1。设儿子为\(k\),则递归solve(k,now-1,ans<<1|1)
  3. \(p\)没有儿子,直接返回(ans,1)(最大值和个数)

最后返回值是返回各个分支的最大值,注意个数的统计。

核心代码如下:

#define pr pair<int,int>
#define mk make_pair
pr query(int p,int ans){
	if(t[p][0]==0&&t[p][1]==0)return mk(ans,1);
	if(t[p][0]&&t[p][1]){
		pr ans1=query(t[p][0],ans<<1);
		pr ans2=query(t[p][1],ans<<1);
		if(ans1.first==ans2.first)return mk(ans1.first,ans1.second+ans2.second);
		return max(ans1,ans2);
	}
	if(t[p][0])return query(t[p][0],ans<<1|1);
	return query(t[p][1],ans<<1|1);
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	n2=1;for(int i=1;i<=n;i++)n2*=2;
	for(int i=1;i<=m;i++)cin>>a[i];
	for(int i=1;i<=m;i++)pre[i]=pre[i-1]^a[i];
	for(int i=1;i<=m;i++)s[i]=get(pre[i])^pre[n]^pre[i];
	for(int i=1;i<=m;i++)insert(s[i]);
	insert(0);
	pr ans=query(1,0);
	cout<<ans.first<<endl<<ans.second<<endl;
}
posted @ 2023-01-11 08:07  spdarkle  阅读(14)  评论(0编辑  收藏  举报