字典树(Trie)

字典树(Trie)

Trie 是一种能够快速插入和查询字符串的多叉树结构。

节点的编号各不相同,根节点编号为 \(0\) ,其他节点用来标识路径。

还可以标记单词插入的次数。边表示字符。

一般情况下,Trie维护字符串的集合,支持以下两种操作:

1.向集合中插入一个字符串。

2.向集合中查询一个字符串。

建字典树

子数组 ch[p][j] 表示从节点 \(p\) 沿着 \(j\) 这条边走的到子节点。

计数数组 cnt[p] 表示以节点 \(p\) 结尾得到单词的插入次数。

节点编号 idx 用来给节点编号。

1.Trie中只有一个根节点,编号为0。

2.从根开始插入,枚举字符串的所有字符。

​ 如果有儿子,那么直接走到儿子。

​ 反之,先创建儿子,再走到儿子。

3.维护计数数组。

int ch[N][26],cnt[N],idx;
void insert(string s){
	int p=0;
	for(auto c:s){
		int j=c-'a';
		if(!ch[p][j]) ch[p][j]=++idx;
		p=ch[p][j];
	}
	cnt[p]++;
}
int query(string s){
	int p=0;
	for(auto c:s){
		int j=c-'a';
		if(!ch[p][j]) return 0;
		p=ch[p][j];
	}
	return cnt[p];
}

0-1 Trie 解决异或和相关问题

将整数转化成2进制后,将其存入Trie中。利用贪心思想即可求出最大异或和。

int ch[N<<5][2],idx;
int sz[N<<5];
void ins(int x){
   for(int i=31,p=0;i>=0;i--){
      int j=x>>i&1;
      if(!ch[p][j]) ch[p][j]=++idx;
      p=ch[p][j];
      sz[p]++;
   }
}

void del(int x){
   for(int i=31,p=0;i>=0;i--){
       int j=x>>i&1;
       p=ch[p][j];
       --sz[p];
   }
}

int query(int x){
   int ret=0;
   for(int i=31,p=0;i>=0;i--){
      int j=((x>>i)&1);
      if(ch[p][j^1]&&sz[ch[p][j^1]]){
         p=ch[p][j^1];
         ret|=(1LL<<i);
      }else p=ch[p][j];
   }
   return ret;
}

这个板子可以解决求出 \(x\) 与集合中的元素选出一个最大异或值。如果想要拿出集合中的元素,只需要 \(query(x)\oplus x\) 即可。

例题: HDU4825]Xor Sum - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

并且多维护了一个 \(sz\) 数组,支持删除集合中的元素。

例题HDU5536]Chip Factory - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

还有一类经典的问题--最大区间异或

给你一个数组求出最大区间异或。根据异或的性质,我们可以维护出前缀异或数组。

那么 \(ans_{l,r}=pre_{l-1} \oplus pre_r\) 。那么,我们只需要把前缀异或数组插入进Trie中。问题就变成了基本的

最大异或和 问题。

对于 最大不相交区间异或和 ,我们可以再多维护一个后缀异或和数组。多处理一次即可。记得清空Trie树。

例题: Codechef REBXOR]Nikitosh and xor - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

经典问题--最长异或路径

\(u\)\(v\) 的路径时,我们会维护每个节点到根节点的距离 \(d_u\)

那么 \(dis_{u,v}=d_u+d_v-2\times d_{LCA_{U,V}}\)

根据异或的性质,多的那部分刚好相互抵消。

因此有 \(xor_{u,v}=d_u \oplus d_v\)

所以我们只要维护一下每个节点到根节点的异或和,剩下的步骤就和最大异或值处理一样。

例题P4551 最长异或路径 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

posted @ 2024-07-09 01:10  Showball  阅读(3)  评论(0编辑  收藏  举报