BZOJ 4260 Codechef REBXOR (区间异或和最值) (01字典树+DP)
<题目链接>
题目大意:
给定一个序列,现在求出两段不相交的区间异或和的最大值。
解题分析:
区间异或问题首先想到01字典树。利用前缀、后缀建树,并且利用异或的性质,相同的两个数异或变成0,从而将前缀操作转化为区间操作,比如:$(a_1 \oplus a_2)\oplus(a_1 \oplus a_2 \oplus a_3 \oplus a_4) = a_3 \oplus a_4$。然后利用简单的$dp$,$predp[i]$记录前$[1~i]$ 任意区间的区间异或最大值(注意不是前缀),$nxtdp$同理记录后缀区间的最大值。
本题空间卡得比较紧,所以没有另外用一个$val[now]$数组记录Trie树前缀节点所表示的值,而是在$query$中用$ans$代替。
#include <bits/stdc++.h> using namespace std; const int N = 4e5+5; typedef long long ll; ll n,pos=1,arr[N],nxt[N*30][2],predp[N],nxtdp[N]; template<typename T> inline T read(T&x) { x = 0;int f = 1; char ch = getchar(); while(ch<'0' || ch>'9') { if(ch == '-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } void Insert(ll x){ int now=1; for(int i=30;i>=0;i--){ int to=(x>>i)&1; if(!nxt[now][to])nxt[now][to]=++pos; now=nxt[now][to]; } } ll query(ll x){ int now=1;ll ans=0; for(int i=30;i>=0;i--){ int to=(x>>i)&1; if(nxt[now][to^1])now=nxt[now][to^1],ans+=(1<<i); //因为最终异或得到的答案就是x中所不包含的数位 else now=nxt[now][to]; } return ans; //return x^val[now]; 因为本题空间卡得紧,所以就不能写成这种形式 } int main(){ read(n); for(int i=1;i<=n;i++)read(arr[i]); for(int cur=0,i=1;i<=n;i++){ Insert(cur^=arr[i]); predp[i]=max(query(cur),predp[i-1]); //因为一个数被异或两次等于0,所以这里利用Trie树的前缀操作来实现区间操作 }//得到 i 的前缀区间异或和的最大值 memset(nxt,0,sizeof(nxt)); for(int cur=0,i=n;i>=1;i--){ Insert(cur^=arr[i]); nxtdp[i]=max(query(cur),nxtdp[i+1]); }//得到 i 的后缀异或区间和的最大值 ll ans=-1; for(int i=1;i<=n;i++)ans=max(ans,predp[i]+nxtdp[i]); printf("%lld\n",ans); }
2019-03-02
作者:is_ok
出处:http://www.cnblogs.com/00isok/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。