传送门:BZOJ 4260
题目大意:
给你 n 个数,让你求两个不相交的区间元素异或后的和的最大值。本题中 n 的上限是 4*10^5.
前置技能: 01字典树。
思路:
首先看下异或的性质,关于异或我们知道: 0^a=a , a^a=0 。前 i 个数的异或和前 j 个数的异或相异或: pre[i]^pre[j] = a[i+1]^a[i+2]^……^a[j],(i<j)。异或的后缀和类似。
于是我们可以先求出异或的前缀 pre[i]和后缀和 suf[i]。dp[i]表示前 i个数中任意区间异或的最大值,可以依次求与 pre[i] 相异或结果的最大值,然后把 pre[i] 插入到 01字典树中。
这样对于每个 pre[i] 他会和之前的 i-1 个异或前缀和的共有部分所抵消,也就相当于是求任意区间的异或结果的最大值了。这样求出了一个区间,同理可利用后缀和求出另一个区间。
你可能好奇,怎么保证两个区间不相交呢?可以通过使前后两个区间一个为不包含第 i个数的前部分区间,一个是包含第 i 个数的后部分区间就可以了。
代码:
#include<stdio.h>
#include<string.h>
#include<iostream>
#define MAXN 400010
using namespace std;
int tol;
int val[MAXN*32];
int ch[MAXN*32][2];
//dp[i]表示前 i个数中任意区间异或的最大值
//pre[i]表示前 i个数的异或结果,即前缀和
//suf[i]表示第 i个数之后的互素异或结果,即后缀和
int dp[MAXN],pre[MAXN],suf[MAXN];
void init()
{ //初始化
tol=1;
ch[0][0]=ch[0][1]=0;
}
void insert(int x)
{
int u=0;
for(int i=32;i>=0;i--)
{
int v=(x>>i)&1;
if(!ch[u][v])
{
ch[tol][0]=ch[tol][1]=0;
val[tol]=0;
ch[u][v]=tol++;
}
u=ch[u][v];
}
val[u]=x;
}
int query(int x)
{
int u=0;
for(int i=32;i>=0;i--)
{
int v=(x>>i)&1;
if(ch[u][v^1]) u=ch[u][v^1];
else u=ch[u][v];
}
return x^val[u];
}
int main()
{
int i,j,n,ans,a[MAXN];
while(~scanf("%d",&n))
{
for(i=1;i<=n;i++) scanf("%d",&a[i]);
pre[0]=suf[n+1]=0;
for(i=1;i<=n;i++) pre[i]=pre[i-1]^a[i]; //前缀和
for(i=n;i>=1;i--) suf[i]=suf[i+1]^a[i]; //后缀和
memset(dp,0,sizeof(dp));
init(); //第一次初始化
insert(pre[0]);
for(i=1;i<=n;i++)
{ //求出 dp[i],即前 i个数的任意区间异或的最大值
dp[i]=max(dp[i-1],query(pre[i]));
insert(pre[i]);
}
init(); //第二次初始化
ans=0;
insert(suf[n+1]);
for(i=n;i>=1;i--)
{ //求出最终结果
//query(suf[i])+dp[i-1]即 i 之后的任意区间异或值和 i之前的任意区间异或值之和
ans=max(ans,query(suf[i])+dp[i-1]); //dp[i-1] 保证了两个区间不相交
insert(suf[i]);
}
printf("%d\n",ans);
}
return 0;
}