CF252A Little Xor

solution 1

此解法复杂度为 O(n3)O(n^3)

由于数据很小,所以我们暴力枚举左右端点,暴力计算前缀和即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,a[N],mx,sum;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			sum=a[i];
			for(int k=i+1;k<=j;k++)
				sum^=a[k];
			mx=max(mx,sum);
		}
	}
	cout<<mx<<endl;
	return 0;
}

如果 nn 的范围扩大到 10001000 呢?

solution 2

此解法复杂度为 O(n2)O(n^2)

我们设这个序列为 a1,a2,,ana_1,a_2,…,a_nsi=a1 xor a2 xor  xor ai=si1 xor ais_i=a_1\ xor\ a_2\ xor\ …\ xor \ a_i=s_{i-1}\ xor\ a_i,其中 s0=0s_0=0

此时, ai xor ai+1 xor  xor aja_i\ xor\ a_{i+1}\ xor\ …\ xor \ a_j

=0 xor (ai xor ai+1 xor  xor aj)=0\ xor\ (a_i\ xor\ a_{i+1}\ xor\ …\ xor \ a_j)

=(a1 xor a2 xor  xor ai1) xor (a1 xor a2 xor  xor ai1) xor (ai xor ai+1 xor  xor aj)=(a_1\ xor\ a_2\ xor\ …\ xor \ a_{i-1})\ xor\ (a_1\ xor\ a_2\ xor\ …\ xor \ a_{i-1})\ xor\ (a_i\ xor\ a_{i+1}\ xor\ …\ xor \ a_j)

=(a1 xor a2 xor  xor ai1) xor (a1 xor a2 xor  xor ai1 xor ai xor ai+1 xor  xor aj)=(a_1\ xor\ a_2\ xor\ …\ xor \ a_{i-1})\ xor\ (a_1\ xor\ a_2\ xor\ …\ xor \ a_{i-1}\ xor\ a_i\ xor\ a_{i+1}\ xor\ …\ xor \ a_j)

=si1 xor sj=s_{i-1}\ xor\ s_j

这就是异或前缀和

这样我们只要暴力枚举 i,ji,j,就只有 O(n2)O(n^2) 的时间复杂度。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,a,s[N],mx; 
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	cin>>a;
    	s[i]=s[i-1]^a;
	}
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
			mx=max(mx,s[j]^s[i-1]);
	cout<<mx<<endl;
	return 0;
}

如果 nn 继续扩大到 104,105,10610^4,10^5,10^6 呢?

solution 3

此解法复杂度为 O(n)O(n)

考虑改进 solution 2。

在求出了异或前缀和的前提下,我们的问题变成了:

在给定的 n+1n+1 个整数 s0,s1,s2,,sns_0,s_1,s_2,…,s_n 中选出两个进行 xorxor(异或)运算,得到的结果最大是多少?

我们考虑所有的二元组 (i,j)(i,j)i<ji<j,那么本题的目标就是在其中找到 si xor sjs_i\ xor\ s_j 的最大值。也就是说,对于每个 i(0in)i(0\le i\le n),我们希望找到一个 j(0j<i)j(0\le j<i),使 si xor sjs_i\ xor\ s_j 最大,并求出这个最大值。

我们可以把每个整数看作长度为 3030 的二进制 0101 串(数值较小时在前面补 00),并且把 s0si1s_0~s_{i-1} 对应的 3030 位二进制串插入一棵 Trie\text{Trie} 树(其中最低二进制位为叶子节点)。

接下来。对于 sis_i 对应的 3030 位二进制串,我们在 Trie\text{Trie} 中进行一次与检索类似的过程,每一步都尝试沿着“与 sis_i 的当前位相反的字符指针”向下访问。若“与 sis_i 的当前位相反的字符指针”指问空节点,则只好访问与 sis_i 当前位相同的字符指针。根据 xorxor 运算“相同得 00,不同得 11”的性质,该方法即可找出与 sis_ixorxor 运算结果最大的 sjs_j

如下页图所示,在一棵插入了 2(010),5(101),7(111)2(010),5(101),7(111) 三个数的 Trie\text{Trie} 中,分别查询与 6(110),3(011)6(110),3(011)xorxor 运算结果最大的数。(为了简便,图中使用了 33 位二进制数代替 3030 位二进制数。)

综上所述,我们可以循环 i=1ni=1~n,对于每个 iiTrie\text{Trie} 树中应该存储了 s0si1s_0~s_{i-1} 对应的 3030 位二进制串(实际上每次 ii 增长前,把 sis_i 插入 Trie\text{Trie} 即可)。根据我们刚才提到的“尽量走相反的字符指针”的检索策略,就可以找到所求的 sjs_j,更新答案。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,a,s[N],trie[N*32][2],tot=1,mx;
void Trie_insert(int x)//插入 
{
	int p=1;
	for(int k=30;k>=0;k--)
	{
		int ch=(x>>k)&1;
		if(trie[p][ch]==0)
			trie[p][ch]=++tot;
		p=trie[p][ch];
	}
}
int Trie_search(int x)//搜索 
{
	int p=1,ans=0;
	for(int k=30;k>=0;k--)
	{
		int ch=(x>>k)&1;
		if(trie[p][!ch])//相反的位 
			p=trie[p][!ch],ans+=1<<k;
        else
			p=trie[p][ch];
	}
	return ans;
}
int main()
{
    cin>>n;
    Trie_insert(s[0]);//s[0]也要插入 
    for(int i=1;i<=n;i++)
    {
    	cin>>a;
    	s[i]=s[i-1]^a;
    	Trie_insert(s[i]);
    	mx=max(mx,Trie_search(s[i]));
	}
	cout<<mx<<endl;
	return 0;
}
posted @ 2021-08-04 19:28  luckydrawbox  阅读(10)  评论(0)    收藏  举报  来源