字典树和可持久化字典树

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;
}
posted @ 2020-08-06 20:02  RiverSheep  阅读(180)  评论(0编辑  收藏  举报