Nikitosh 和异或

https://loj.ac/problem/10051

题目描述

  给出\(N\)个数的序列\(A\),要求求出两段连续的子序列(互不重叠),他们内的数异或后值的和最大。

思路

  这道题其实是The XOR Largest Pair的进阶版,我们知道暴力枚举显然无法实现,所以就要用到一些优化。首先我们考虑如果选出一段连续子序列最大如何做。我们已知一个很显然的性质\(x\bigoplus x=0\)\(0\bigoplus x=x\),所以对于一段子序列我们可以用类似维护前缀和的方法,用\(s[r]\bigoplus s[l-1]\)取出\([l,r]\)这一段的异或值,因为前几个数可以异或为\(0\)。不过暴力枚举子序列的复杂度我们仍无法承受,但我们知道快速从一堆数中求出一对数异或值最大。所以这就等价于从\(s[1..r-1]\)找出和\(s[r]\)异或起来最大值,再取个\(max\)即可,我们也可用字典树维护。

  接下来就是两段不重叠的序列,我们知道这一定由两段\([l1,r1]\)\([l2,r2]\),因此他们一定可以从一个端点分为两部分,所以我们只要正着做一遍,倒着做一遍,得到两个数组\(l,r\),那么答案就是\(l[i]+r[i+1]\)中的最大值。

代码

#include <bits/stdc++.h>
using namespace std;
const int MAXN=4e5+5;
int ch[MAXN<<5][3],tot,a[MAXN],l[MAXN],r[MAXN];
void insert(int x)
{
    int u=1;
    for(int i=1<<30;i;i>>=1)
    {
        int num=(x&i)?1:0;
        if(!ch[u][num])ch[u][num]=++tot;
        u=ch[u][num];
    }
}
int find(int x)
{
    int u=1,ans=0;
    for(int i=1<<30;i;i>>=1)
    {
        int num=(x&i)?0:1;
        if(ch[u][num])
        {
            ans+=i;
            u=ch[u][num];
        }
        else u=ch[u][!num];
    }
    return ans;
}
int main() 
{
    int n,sum=0;
    scanf("%d",&n);
    tot=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum=sum^a[i];
        insert(sum);
        l[i]=max(l[i-1],find(sum));
    }
    memset(ch,0,sizeof(ch));
    sum=0;tot=1;
    for(int i=n;i>0;i--)
    {
        sum=sum^a[i];
        insert(sum);
        r[i]=max(r[i+1],find(sum));
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,l[i]+r[i+1]);
    printf("%d",ans);
    return 0;
}
posted @ 2019-11-03 17:55  fbz  阅读(507)  评论(0编辑  收藏  举报