1. 程式人生 > >bzoj3261 最大異或和(可持久化Tire樹)

bzoj3261 最大異或和(可持久化Tire樹)

題目

傳送門
給定一個非負整數序列{a},初始長度為N。有M個操作,有以下兩種操作型別:
1、A x:新增操作,表示在序列末尾新增一個數x,序列的長度N+1。
2、Q l r x:詢問操作,你需要找到一個位置p,滿足l<=p<=r,使得:a[p] xor a[p+1] xor … xor a[N] xor x 最大,輸出最大是多少。

題解

考慮字首和
sum[i]表示1.2….i的字首的異或和,那麼題目讓求的就是sum[i]^sum[n]^x,選取一個i求其最大值.
每次就用X^=sum[n],再用這樣的X在L到R範圍內找一個sum使其異或和最大
這就轉化成了在一堆數裡面選一個數與指定數異或和最大的經典trie樹貪心的問題了

那麼我們對於每一個新加的節點,都建立一棵tire樹是不可以被接受的,類比上面可持久化線段樹,主席樹的思想,將sum[i]轉化為二進位制數,然後建立可持久化trie樹,根據(sum[n]^x)在tire樹上查詢;貪心:儘量使異或和為1,不行的話為0;

對於相鄰的兩棵樹,只有一個數不同而已,一個數在trie上對應的就是一條鏈,建樹時我們沿著這條鏈走,把這條鏈構造進新樹裡,其餘結構全部指向舊樹
就是說新樹只有這一條鏈的部分是新建的,而其他部分全部與舊樹共享,類比主席樹

程式碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; const int maxn=600005; const int sz=25; int ans,num,n,m,x,y,k,cnt,tot,root[maxn],sum[maxn*30],ch[maxn*30][2];//tot異或和 ch[][]左右孩子 void insert(int &now,int x,int dep)//now根 x當前的異或和,dep深度 { sum[++num]=sum[now]+1; ch[num][0]=ch[now][0]; ch[num][1]=ch[now][1]; now=num; if
(dep==-1) return; int k=(x>>dep)&1;//x的第dep位是否為1;區分左右 if (!k) insert(ch[now][0],x,dep-1); else insert(ch[now][1],x,dep-1); } void ques(int L,int R,int x,int dep) { if (dep==-1) return; int k=(x>>dep)&1; if (sum[ch[R][k^1]]-sum[ch[L][k^1]]>0) { ans|=1<<dep;//或操作有加的一點意思(自行參悟) ques(ch[L][k^1],ch[R][k^1],x,dep-1); } else ques(ch[L][k],ch[R][k],x,dep-1); } int main() { scanf("%d%d",&n,&m); for (int i=1; i<=n; i++) { scanf("%d",&x); tot^=x; root[i]=root[i-1]; insert(root[i],tot,sz-1); } cnt=n; for (int i=1;i<=m;++i) { char opt=getchar(); while (opt!='A'&&opt!='Q') opt=getchar(); if (opt=='A') { scanf("%d",&x); tot^=x; root[++cnt]=root[cnt-1]; insert(root[cnt],tot,sz-1); } else { int l,r; scanf("%d%d%d",&l,&r,&x); ans=0; --l,--r;l=max(l,0);r=max(r,0); ques(root[l-1],root[r],tot^x,sz-1); if (l==0) ans=max(ans,tot^x); printf("%d\n",ans); } } return 0; }

總結

與異或有關的應該聯想到Tire樹
可持久化資料結構的應用