BZOJ 4260 Codechef REBXOR trie树+树状数组

Description

Input

输入数据的第一行包含一个整数N,表示数组中的元素个数。
第二行包含N个整数A1,A2,…,AN。

Output

输出一行包含给定表达式可能的最大值。

Sample Input

5
1 2 3 1 2

Sample Output

6

HINT

满足条件的(l1,r1,l2,r2)有:(1,2,3,3),(1,2,4,5),(3,3,4,5)。

对于100%的数据,2 ≤ N ≤ 4*105,0 ≤ Ai ≤ 109。










好题……不怎么难。。

先考虑只取一段的情况,
首先对原来的数组作xor的前缀和。
我们知道xor也是满足这样的性质的:
a[x]^a[x+1]^……^a[y]=sum[x-1]^sum[y]
我们可以枚举连续序列的尾部y,那么也就是说要找出一个x,
使得sum[x-1]^sum[y]最大。
xor不像加法或者减法,对于不同的数字,和它异或起来大的数字是不同的。
这个时候去考虑一下二进制方面,
假如sum[y]=0011(二),那么sum[x-1]尽量是谁呢?
当然是1100(二)了。
每一位都尽量找不同的那一位即可。。
那假如没有sum[x-1]=1100呢?1000和0100谁更优呢。。
二进制的从后往前第x位带有2^(x-1)的权值,
也就是011111<100000...
所以我们从高位到低位,能有对应的就选择这个对应的,不然只好选择另一个。

这个过程可以用trie树实现,每个节点连向儿子有两种边,一种是0,一种是1;
比如走1的边,意义就是二进制上这一位是1.
每次处理完sum[x],就把sum[x]转化为二进制插入到trie里就好了。
假如一个节点u,sum[y]的目前位置的二进制位数字是p,
如说u的son[p^1]非空,那么u走向son[p^1],不然u走向son[p],
最后把数字加起来,return的就是和sum[y]xor最优的了。。


-> v ->这个地方竟然说了这么多……
那题目要求两段不重复的,要怎么办呢?
首先对于第一段,枚举一个结尾的话,很容易就用刚才的方法求出最优值了;
而对于第二段,如果是枚举结尾i,我们可以发现不一定是刚才的方法最优了……
因为我们要找的是{以k结尾的第一段的最大值+sum[i] xor sum[j-1]}
可以用下图来表示:
可以发现这个时候就有点不可做了……
但是我们可以发现:第一段枚举结尾来确定,因为第一段的之前的部分对后面没有了影响;
很容易地,能够想到优化:
枚举第二段的开头,也就是图中的j,
然后在trie树里,我们可以直接找出来一个最优的sum[i](因此我们要从尾往头线扫);
而对于F[k],我们仍然是取最大的,这个F[k]的最大值我们用树状数组维护即可。

没有任何难度啊喂我写这么多……




#include<bits/stdc++.h>
using namespace std;
const int
    N=400005,
    MAXLEN=31;
int n,len,cnt;
int ejz[MAXLEN+5],a[N],sum[N];
int tr[N],f[N],trie[N*MAXLEN][2];
void update(int x,int y){
    for (int i=x;i<=n;i+=i&-i) tr[i]=max(tr[i],y);
}
int getmaxf(int x){
    int y=0;
    for (int i=x;i;i-=i&-i) y=max(y,tr[i]);
    return y;
}
void jzzh(int x){
    len=0;
    while (x)
        ejz[++len]=(x&1),x>>=1;
    int tmp=len+1;
    for (int i=tmp;i<=MAXLEN;i++) ejz[i]=0;
    len=MAXLEN;
}
void insert(int x){
    int now=0;
    jzzh(x);
    for (int i=len;i;i--)
        if (trie[now][ejz[i]]) now=trie[now][ejz[i]];
            else now=trie[now][ejz[i]]=++cnt;
}
int get_xor_max(int x){
    int now=0,an=0;
    jzzh(x);
    for (int i=len;i;i--){
        if (trie[now][!ejz[i]])
            now=trie[now][!ejz[i]],an+=(!ejz[i])*(1<<(i-1));
         else
            now=trie[now][ejz[i]],an+=ejz[i]*(1<<(i-1));
    }
    return an;
}
void Pre(){
    sum[0]=0;
    for (int i=1;i<=n;i++) sum[i]=sum[i-1]^a[i];
    cnt=0,insert(0);
    f[1]=sum[1],insert(sum[1]);
    for (int i=2;i<=n;i++){
        f[i]=sum[i]^get_xor_max(sum[i]);
        insert(sum[i]);
    }
    for (int i=1;i<=n;i++) update(i,f[i]);
}
void solve(){
    cnt=0;
    memset(trie,0,sizeof(trie));
    insert(sum[n]);
    int ans=0;
    for (int j=n-1;j>1;j--){
        int tmp=sum[j]^get_xor_max(sum[j]);
        ans=max(ans,getmaxf(j)+tmp);
        insert(sum[j]);
    }
    printf("%d\n",ans);
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    Pre();
    solve();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值