1. 程式人生 > 實用技巧 >可持久化資料結構-可持久化01Trie

可持久化資料結構-可持久化01Trie

更好的閱讀體驗

前置知識:


來看這樣一道題目:

對於長度為\(n\leq 2*10^5\)的數列a,要求進行\(m\leq 10^5\)次詢問,給定\(l,r,x\),求\(i∈[l,r],max\{a_i⊕x\}\),\(a_i\leq 2^{32}\)

如果是初學01Trie,可能一眼就覺得這是個裸題,但是並沒有那麼簡單,回憶我們01Trie的操作,每次查詢時是在一棵完整的Trie樹上進行查詢,這裡的完整指的是,如果我們把\(a_1\)加進Trie,我們就無法做到一定不走\(a_1\)

於是我們有一個樸素的想法:每次插入一個值前備份

當前Trie樹上的資訊。這裡的資訊即val,val[u]記錄結點u被經過的次數,那麼就用val[u][x]記錄\(a_x\)插入後\(u\)結點的\(val\)

每次查詢[l,r]的資訊,我們依然是從根節點出發,當我們想要走到結點u時,需要先判斷 val[u][r]-val[u][l-1]是否大於0,如果是,那麼才能走,因為這說明在[l,r]區間內有這樣一個數\(a_i\)支援我們走向結點u,這是顯然的。

但是Trie的空間消耗本來就有點大,1e5的資料為了保險要開到3e6左右的結點。對於每個結點我們還要記錄n個版本的資訊,空間直接爆炸掉。

那麼我們考慮利用可持久化的思想來優化這個過程。

我們回憶起,每次插入一個數,最多新增的結點數目是log級別的,正如我們的線段樹。

那麼也就是說,每次插入會引起val改變的結點數目只有log個,其它結點的資訊是沒有必要再進行一次備份的。

考慮插入每個新的結點時加入一個新的root,即對於每個數\(a_i\)都有不同的\(rt_i\)

假設插入數字的這一位是v,那麼我們總是新建v分支的結點,並把!v分支的結點連向上一個版本的兒子。

假設我們要查詢區間[l,r],我們就同時從\(rt_r,rt_{l-1}\)出發,按照x的每一位依次遞迴,判斷一個結點u是否合法還是和上面一樣,用第r棵的減去第l-1棵的即可。

並且由於我們的val也是從上一棵trie繼承過來的,每次插入依然是log(n)的,新增的結點也是log(n)級別的。資訊的繼承也不會出現問題,為什麼呢?樸素的Trie其實也是這樣的,只是\(rt_{1...n}\)

都是同一個位置,上一個版本的繼承過來時也就不需要兩個結點一起遞迴,所以繼承資訊這一塊並沒有本質上的區別,自然可以用上述方法進行計算。

程式碼:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x(0),f(1);char ch(getchar());
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f;
}
int idx=1;
int c[7000010][2];
int val[7000010];
int rt[200010];
void insert(int x,int b){
	int u=rt[b],u2=rt[b-1];
	for(int i=31;~i;i--){
		int v=(x>>i)&1;
		c[u][v]=++idx;
		if(u2)
			val[c[u][v]]=val[c[u2][v]]+1,
			c[u][!v]=c[u2][!v],val[c[u][!v]]=val[c[u2][!v]];
		else val[c[u][v]]=1,c[u][!v]=0;
		u=c[u][v];
		u2=c[u2][v];
	}
}
int ask(int l,int r,int x){
	int ans=0;
	int u1=rt[r],u2=rt[l-1];
	for(int i=31;~i;i--){
		int v=(x>>i)&1;
		if(val[c[u1][!v]]>val[c[u2][!v]]){
			u1=c[u1][!v],u2=c[u2][!v];
			ans+=(1<<i);
		}
		else u1=c[u1][v],u2=c[u2][v];
	}
	return ans;
}
int main(){
	freopen("hugclose.in","r",stdin);
	freopen("hugclose.out","w",stdout);
	rt[0]=1;
	int n(read()),q(read());
	for(int i=1;i<=n;i++){
		int a(read());
		rt[i]=++idx;
		insert(a,i);
	}
	while(q--){
		int x(read()),l(read()),r(read());
		printf("%d\n",ask(l+1,r+1,x));
	}
	return 0;
}