传送cf题目
传送牛客网题目看了半个多小时的题解才搞明白,一下题解为自己的心得
参考博客(这两个讲的很详细):
参考一
参考二
题意:有一个长度有n的整数序列,你要在这个序列中选择一个前缀和后缀,前后缀不想交,前后缀任何一方都可以为空,问你前缀异或值与后缀异或值的异或最大是多少?
比如 一组数 1 2 3 4 5 6 你可以选择前缀为1 2,前缀异或和为3 选择后缀为4 5 6,后缀异或和为7
(前缀异或和)与(后缀异或和)异或值为4,但此时4并不是最大情况,求出最大情况
思路:
首先讲个小例题:
给一个数 a,还有一堆数,怎么在这一堆数中找出一个数 b,a 和 b 的异或值最大?
最暴力的方法无疑是(老办法) 枚举,枚举每一个b,但这样肯定不行(不然我写这个博客干什么) ,想想计算机的本质是啥?对,二进制。我们把a与这堆数转化成二进制,把后面这堆数装进一个字典树,当然要从最高位装,比如这堆数是123456,如图根据异或规则不同为一,所以我们要使a与b异或最大,就要让b尽可能与a不同,a已经给定,b已经形成字典树,我们就从字典树root开始,尽量找出于a当前位置不同的数,直到找到最低位为止,那么这样找到的b满足条件。
回到这个题:
首先这些n个数组成一个区间w,w的全部异或结果是定值K,所以问题可以改成在区间w中取连续一段区间m,m的异或结果为X,m的前部分就成为区间w的前缀,后半部分就是区间w的后缀。
我们知道相同的数异或为零,那么X与K异或,重复的那部分区间异或后为零,就相当于是我们题目所求的
,所以就是求什么情况下X xor K最大。
发现现在的情况和一开始讲的例题很像了吧,我们假设有个Y,Y与K的每一个二进制相异,我们就要让X尽可能接近Y。
怎么实现呢?也是建一个字典树,将f[i]放进去(f[i]=a[1] ^ a[2] ^ a[3] ^ ...^ a[i]),那么f[i]^f[j]=a[i+1] ^ a[i+2] ^ ... ^aj可以表示i+1到j这段区间的异或值。
我们枚举区间m的结尾,每次用一个f[i]去匹配一个f[k],使得f[k]^f[i]的值在高位上尽可能去接近Y,这样就相当于选出区间[k+1,i]de异或值作为X,每次在[1,i]区间内匹配出来一个最佳区间后,不断更新答案。
看懂了吗?这些神奇的操作,巧妙利用字典树(工具人石锤)来匹配。
(太晚了就不重新打代码了,借用下参考二的代码)
#include <iostream> #include <cstring> #include <cstdio> using namespace std; typedef long long LL; const int N=100006; LL A[N], ans, cur, New; int n; struct Trie_Tree { struct node { int next[2]; void init() { next[0]=next[1]=-1; } } T[64*N]; int tot; void Insert(LL val) { for(int i=63, u=0; i>=0; i--) { int id=(((1LL)<<i)&(val))!=0; if(T[u].next[id]==-1) { T[tot].init(); T[u].next[id]=tot++; } u=T[u].next[id]; } } LL Find(LL val) { LL res=0; for(int i=63, u=0; i>=0; i--) { int id=(((1LL)<<i)&(val))==0; if(T[u].next[id]==-1) id^=1; res=res*2LL+(LL)id; u=T[u].next[id]; } return res; } } tree; int main() { scanf("%d", &n); cur=0, New=0; for(int i=0; i<n; i++) scanf("%I64d", &A[i]), cur^=A[i]; tree.T[0].init(), tree.tot=1, ans=cur; tree.Insert(0LL); for(int i=0; i<n; i++) { New^=A[i], cur^=A[i]; tree.Insert(New); LL temp=tree.Find(cur); ans=max(ans, temp^cur); } printf("%I64d\n", ans); return 0; }