1. 程式人生 > 實用技巧 >可持久化字典樹學習筆記

可持久化字典樹學習筆記

引子

我們回憶一下,可持久化可以解決哪些問題?

我們通常用可持久化來解決區間詢問,將區間轉化成歷史版本

可持久化字典樹

加入我們有\(4\)個單詞\(cxc,cxd,cyc,cyt\)

我們用每一個版本代表插入之後的字典樹,我們就得到了如下的圖。

類似的,我們也可以得到可持久化01字典樹建樹的方法

例題

最大異或和

我們首先想到字首和,所以我們用一個01trie樹維護異或的字首和,然後考慮詢問

對於右端點,我們直接以\(root_r\)為根來進行查詢

關鍵是左端點,我們對於每一個節點,我們記錄一個\(last_i\)陣列,表示以\(i\)為根節點時,最大的版本編號。

這時我們在查詢的時候判斷一下,這個節點的\(last\)

是否低於\(l\)

我們就可以輕鬆的打出這樣程式碼

#include<iostream>
using namespace std;
const int N = 600010;
int trie[N * 24][2], last[N * 24]; // last[i]表示結點以i為根節點的子樹中最大的版本編號 
int s[N], root[N], n, m, tot;


void insert(int i, int k, int p, int q) //第i個串s[i]的第k位, p是上一個版本, q是當前版本 
{
	if(k < 0) 
	{
		last[q] = i; //第i個字串出現的最後一個版本(版本最後會轉化成s序列的區間) 
		return;
	}
	int c = s[i] >> k & 1;
	if(p) 
		trie[q][c ^ 1] = trie[p][c ^ 1]; //只有0,1兩個孩子,q自己的孩子是c,則連到p的是c^1 
	trie[q][c] = ++tot; //新建結點 
	insert(i, k - 1, trie[p][c], trie[q][c]);
	last[q] = last[trie[q][c]];
	return ;
}

int query(int cur, int val, int k, int left) //left左邊界 
{
	if(k < 0) //二進位制數位到最低位 
		return s[last[cur]] ^ val;
	int c = val >> k & 1;
	if(last[trie[cur][c ^ 1]] >= left)
		return query(trie[cur][c ^ 1], val, k - 1, left);
	return query(trie[cur][c], val, k - 1, left);
}

int main() 
{
	cin >> n >> m;
	last[0] = -1;
	tot = 1;
	root[0] = tot;
	insert(0, 23, 0, root[0]);
	for(int i = 1; i <= n; i++) 
	{
		int x;
		cin >> x;
		s[i] = s[i - 1] ^ x;
		root[i] = ++tot;
		insert(i, 23, root[i - 1], root[i]); //10^7次方的底數23 
	}
	for(int i = 1; i <= m; i++) 
	{
		string op;
		cin >> op;
		if(op == "A") 
		{
			int x; 
			cin >> x;
			root[++n] = ++tot;
			s[n] = s[n - 1] ^ x;
			insert(n, 23, root[n - 1], root[n]);
		}
		else 
		{
			int l, r, x;
			cin >> l >> r >> x; 
			cout << query(root[r - 1], x ^ s[n], 23, l - 1) << "\n";
		}
	}
	return 0; 
}