1. 程式人生 > 實用技巧 >字典樹和可持久化字典樹

字典樹和可持久化字典樹

Trie

\(Trie\)的結構非常好懂,我們用\(\delta(u,v)\)表示結點\(u\)\(v\)字元指向的下一個結點,或著說是結點\(u\)代表的字串後面新增一個字元\(c\)形成的字串的結點。(\(c\)的取值範圍和字符集大小有關,不一定是 \(0 \sim 26\)。)

\(Trie\)的簡圖:

\(Code\)

插入

void insert(int x) //插入x
{
  for (int i = 30, u = 1; i >= 0; --i) 
  {
    int c = ((x >> i) & 1);
    if (!ch[u][c]) ch[u][c] = ++tot;
    u = ch[u][c];
  }
}

查詢

void get(int x) {
  int res = 0;
  for (int i = 30, u = 1; i >= 0; --i) {
    int c = ((x >> i) & 1);
    if (ch[u][c ^ 1]) {
      u = ch[u][c ^ 1];
      res = ...... //根據題意求答案
    } else
      u = ch[u][c];
  }
  ans = std::max(ans, res);
}

可持久化字典樹

可持久化 \(Trie\) 的方式和可持久化線段樹的方式是相似的,即每次只修改被新增或值被修改的節點,而保留沒有被改動的節點,在上一個版本的基礎上連邊,使最後每個版本的 \(Trie\)

樹的根遍歷所能分離出的 \(Trie\) 樹都是完整且包含全部資訊的。

大部分的可持久化 \(Trie\) 題中,\(Trie\) 都是以 \(01-Trie\) 的形式出現的。

\(Code\)

void update(int u,int v,int x)//新建版本u,上一個版本是v,插入x
{
	for (int i = 30; i >= 0; i--)
	{
		int c = (x >> i) & 1;
		sum[u] = sum[v] + 1;//sum[x] 代表連x的邊有多少個數經過
		ch[u][c ^ 1] = ch[v][c ^ 1];
		ch[u][c] = ++tot;
		u = ch[u][c],v = ch[v][c];
	}
	sum[u] = sum[v] + 1;
}

查詢區間,只需要利用字首和和差分的思想,用兩棵字首 \(Trie\) 樹(也就是按順序新增數的兩個歷史版本)相減即為該區間的線段樹。再利用動態開點的思想,不新增沒有計算過的點,以減少空間佔用。

int query(int u,int v,int x)//求區間[u + 1,v]關於x的答案 
{
	int res = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (x >> i) & 1,p = sum[ch[v][c ^ 1]] - sum[ch[u][c ^ 1]];
		if (p) res = ...........;//根據題意求答案
		else u = ch[u][c],v = ch[v][c];
	}
	return res;
}