1. 程式人生 > >BZOJ 3261 淺談可持久化TRIE樹最大連續異或和

BZOJ 3261 淺談可持久化TRIE樹最大連續異或和

這裡寫圖片描述
世界真的很大
trie樹貪心求最大異或和大概也就是那麼回事了
但是對於區間的查詢就不是那麼容易的了
考慮主席樹的思想,怎麼得到區間的值域的
這就是可持久化的trie樹
說來容易
指標教做人哪
看題先:
description:

給定一個非負整數序列 {a},初始長度為 N。       
有   M個操作,有以下兩種操作型別:

1 、A x:新增操作,表示在序列末尾新增一個數 x,序列的長度 N+12 、Q l r x:詢問操作,你需要找到一個位置 p,滿足 l<=p<=r,使得:

a[p] xor a[p+1] xor ... xor a[N] xor x 最大,輸出最大是多少。 

input:

第一行包含兩個整數 N  ,M,含義如問題描述所示。   
第二行包含 N個非負整數,表示初始的序列 A 。 

接下來 M行,每行描述一個操作,格式如題面所述。   

output:

假設詢問操作有 T個,則輸出應該有 T行,每行一個整數表示詢問的答案。

首先考慮怎麼把連續的異或和轉化掉
考慮連續一段的數字之和的轉化,除了線段樹之外的高階東西,前(後)綴和這個東西在這種不修改的情況下就要優秀的多了
[L,R]的數字之和可以轉化成sum[R]-sum[L-1]或last[L]-last[R+1],而考慮異或和這種東西,如果一個數連續異或偶數次就是0,而任何數異或0都是本身,除了0自己
所以如果處理一段連續的異或和的話,就可以考慮用異或前(後)綴和來處理了,而且由於有新增的操作,只會影響字尾而不會影響字首,如果使用字尾的話每次修改都需要把前面的字尾全部修改一遍,這是不能接受的
既然能達到一樣的區間處理效果,儘管每次求的是最大的字尾,但我們仍然選擇用字首維護
每次就用X^=sum[n],再用這樣的X在L到R範圍內找一個sum使其異或和最大
這就轉化成了在一堆數裡面選一個數與指定數異或和最大的經典trie樹貪心的問題了
但我們不可能每次詢問都對指定區間建一顆trie樹,這樣詢問的代價就是O(n)的了,這是不能接受的。我們希望能夠預處理出每次詢問的trie樹,而詢問優勢線上的,
可持久化trie樹就應運而生了
考慮可持久化線段樹,主席樹的實現方式,將R的trie樹減去L-1的trie樹,類比主席樹,得到的就應該是L到R區間的所有數得到的trie樹,而這樣的一顆顆trie樹是可以預處理的,而每次在數列末尾加數字時再新建一顆trie樹,就可以動態的維護這樣的一顆顆樹了
大概思想是有了,接下來考慮實現方法
還是要類比主席樹
每一個點都建一顆trie樹,存的是1到這個點的所有數的二進位制的01串,而對於每一個點都重新建一顆這樣的樹,時間是O(n^2)的,不能接受的,而類比主席樹,每一個點對應的樹和前一顆樹不同的地方只有一個數而已,完全沒有必要重新建一棵樹,這樣空間時間上都能省一筆
可持久化trie樹的大概思想好像差不多了
那麼我們現在需要考慮的就只有具體的建樹思路了
對於相鄰的兩棵樹,只有一個數不同而已,一個數在trie上對應的就是一條鏈,建樹時我們沿著這條鏈走,把這條鏈構造進新樹裡,其餘結構全部指向舊樹
就是說新樹只有這一條鏈的部分是新建的,而其他部分全部與舊樹共享,類比主席樹
還剩下查詢操作,關鍵是判斷在當前區間內有沒有trie樹的這條鏈,考慮主席樹是怎麼做的,對每一個節點開一個域cnt,記錄這個節點以下有幾個數,每次按trie貪心的方法查詢時,判斷R樹的節點cnt值減去L-1樹的節點的cnt值為不為0,不為0就說明區間內有這麼一個數可以走
類比主席樹
大概想一下,具體化一下具體的程式碼就可以開始寫的,還是挺好寫的,只能說指標教做人哪
學新東西時還是注意一下細節
首先是指標寫法,防RE的辦法就不說了,很簡單,注意trie樹insert時使用的是遞迴還是while,是有區別的,while需要另設一個指標,不能直接用root跳
還有就是邊界問題,一定要在trie樹裡一開始插入一個0,這很重要,因為如果取1到n為值的話,應該是sum[n]^sum[0],如果trie樹裡沒有0的話,就無法實現這一步操作,我的做法是直接多建一棵樹,往裡面插值0,然後將n棵樹依次往後推一位,這樣第一棵樹的值就是0了,每次查的時候就查L-1到R就行了,不然應該是L-2到R-1才對(這個也很重要,因為L到R選擇一個字尾,是指用1到X的異或和異或上sum[L-1]到sum[R-1],而想要得到包含sum[L-1]和sum[R-1],不包含sum[R]

的trie樹,就要用第R-1顆樹減去L-2顆樹,而我們在之前多建了一個點,所以查R到L-1就行了)
完整程式碼:

#include<stdio.h>
#include<cstdio>
#include<algorithm>
using namespace std;

struct node
{
    int cnt;
    node *ch[2];
}pool[20000010],*tail=pool,*root[600010],*null;

int n,m,sum[600010];
char ss[10];

node *newnode()
{
    node *nd=++tail;
    nd->cnt=0
; nd->ch[0]=nd->ch[1]=null; return nd; } void init() { null=++tail; null->cnt=0; null->ch[0]=null->ch[1]=null; } void insert(node *ne,node *&nd,int x) { nd=newnode(); node *now=nd; for(int i=24;i>=0;i--) { now->ch[0]=ne->ch[0],now->ch[1]=ne->ch[1]; now->cnt=ne->cnt+1; int idx=(x&(1<<i))>0; now->ch[idx]=newnode(); now=now->ch[idx];ne=ne->ch[idx]; } now->cnt=ne->cnt+1; } int query(node *ne,node *nd,int x) { int num=0; for(int i=24;i>=0;i--) { int idx=(x&(1<<i))>0; if(nd->ch[!idx]->cnt-ne->ch[!idx]->cnt) { num|=(1<<i); ne=ne->ch[!idx],nd=nd->ch[!idx]; } else ne=ne->ch[idx],nd=nd->ch[idx]; } return num; } int main() { init(); scanf("%d%d",&n,&m); n++; for(int i=2;i<=n;i++) { scanf("%d",&sum[i]); sum[i]^=sum[i-1]; } root[0]=newnode(); for(int i=1;i<=n;i++) insert(root[i-1],root[i],sum[i]); while(m--) { scanf("%s",ss); if(ss[0]=='A') { scanf("%d",&sum[++n]); sum[n]^=sum[n-1]; insert(root[n-1],root[n],sum[n]); } else { int L,R,X; scanf("%d%d%d",&L,&R,&X); X^=sum[n]; printf("%d\n",query(root[L-1],root[R],X)); } } return 0; } /* Whoso pulleth out this sword from this stone and anvil is duly born King of all England */

嗯,就是這樣