news 2026/10/24 20:19:58

保姆级教学——字典树

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
保姆级教学——字典树

字典树

字典树的原理是复用前缀信息,所以字典树又叫前缀树。

构建过程

这里只介绍基本的构建框架,因为字典树的能实现的功能很多,所以结点信息种类也很多,不可能把所有的信息都写上,所以只写框架,后续再根据题目自己补充。

假设字符集是a ∼ z \mathrm a\sim\mathrm za∼z共26 \mathrm {26}26个字符最开始字典树有一个根结点1 \mathrm 11,然后这个根结点需要维护26 2626条边( c h a r , v ) (\mathrm {char},\mathrm v)(char,v),表示这条边对应的字符种类,以及这条边的另一个端点v \mathrm vv。最初26 \mathrm {26}26条边都不存在。

加入字符串

最初我们在树根,设其深度为d e e p \mathrm {deep}deep,根节点的深度为0 \mathrm 00,全局维护一个结点池c n t \mathrm {cnt}cnt,初值为1 \mathrm 11。我们要将字符串s \mathrm ss加入树中:

  • 对于当前字符s d e e p \mathrm {s_{deep}}sdeep​,检查当前结点是否存在对应字符的边( s d e e p , v ) \mathrm {(s_{deep},v)}(sdeep​,v);
  • 如果存在,则前往下一个结点v \mathrm vv,重复检查过程;
  • 如果不存在,那么给当前结点建边( s d e e p , c n t + 1 ) \mathrm {(s_{deep},cnt+1)}(sdeep​,cnt+1),然后跳转到下一个结点c n t + 1 \mathrm {cnt+1}cnt+1;
  • 直到访问到字符串最后一位字符。

查询字符串是否存在

最初我们在树根,我们查询字符串s \mathrm ss是否在树中出现过。

  • 对于当前字符s d e e p \mathrm {s_{deep}}sdeep​,检查当前结点是否存在对应字符的边( s d e e p , v ) \mathrm {(s_{deep},v)}(sdeep​,v);
  • 如果存在,则前往下一个结点v \mathrm {v}v;
  • 如果不存在,直接报告不存在;
  • 如果访问到最后一个字符仍存在,那么报告存在。

删除字符串

这个具体题目有具体的删除方式,主要是看我们给每个结点定义了怎样的信息,参照之前每个字符串是如何贡献的,删除字符串就是将字符串的贡献取消。

模板1

模版题1

查询某个字符串是否出现,以及是否出现过两次。

需要给每个结点加一些额外信息,即e n d \mathrm{end}end,其中e n d \mathrm {end}end表示当前结点以末尾的形式出现的次数。

#include<bits/stdc++.h>usingnamespacestd;//#pragma GCC optimize(2)#defineintlonglong#defineendl'\n'#definePIIpair<int,int>#defineINF1e18constintM=1e5;constintN=1e4;inttr[50*N+50][27];intEnd[50*N+50];intcnt=1;intexist(string&s){intnext=1;for(inti=0;i<s.size();i++){if(!tr[next][s[i]-'a']){return0;}next=tr[next][s[i]-'a'];}returnEnd[next];}voidadd(string&s){intnext=1;for(inti=0;i<s.size();i++){if(!tr[next][s[i]-'a']){tr[next][s[i]-'a']=++cnt;}next=tr[next][s[i]-'a'];}End[next]++;return;}voidslove(){intn;cin>>n;for(inti=1;i<=n;i++){string s;cin>>s;add(s);}intm;cin>>m;for(inti=1;i<=m;i++){string s;cin>>s;intt=exist(s);if(t!=0)add(s);if(t==0)cout<<"WRONG"<<endl;elseif(t==1)cout<<"OK"<<endl;elsecout<<"REPEAT"<<endl;}}signedmain(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();}

模板2

判断在所有字符串中,是否存在一个字符串是另一个字符串的前缀。

实现这个功能,我们需要给结点维护两个信息,一个是p a s s \mathrm {pass}pass,一个是e n d \mathrm {end}end。

p a s s \mathrm {pass}pass统计的是,当前结点被经过多少次,e n d \mathrm {end}end统计的是,以当前结点为终点的字符串有多少个。

那么我们只需要判断,是否存在一个结点的e n d \mathrm {end}end大于0 \mathrm 00的情况下,p a s s \mathrm {pass}pass至少为2 \mathrm 22。

所以只需要按顺序添加字符串,然后判断这个字符串的末尾结点的p a s s \mathrm{pass}pass是否大于1 \mathrm{1}1。

#include<bits/stdc++.h>usingnamespacestd;//#pragma GCC optimize(2)#defineintlonglong#defineendl'\n'#definePIIpair<int,int>#defineINF1e18constintM=1e5;constintN=1e4;inttr[50*N+50][27];intEnd[50*N+50];intPass[50*N+50];intcnt=1;intexist(string&s){intnext=1;for(inti=0;i<s.size();i++){if(!tr[next][s[i]-'0']){return0;}next=tr[next][s[i]-'0'];}returnnext;}voidadd(string&s){intnext=1;Pass[next]++;for(inti=0;i<s.size();i++){if(!tr[next][s[i]-'0']){tr[next][s[i]-'0']=++cnt;}next=tr[next][s[i]-'0'];Pass[next]++;}End[next]++;return;}voidslove(){for(inti=1;i<=cnt;i++){for(intj=0;j<10;j++){tr[i][j]=0;}Pass[i]=0;End[i]=0;}cnt=1;intn;cin>>n;intflag=0;vector<string>v(n+1);for(inti=1;i<=n;i++){cin>>v[i];add(v[i]);}for(inti=1;i<=n;i++){intnext=exist(v[i]);if(Pass[next]>1){flag=1;}}if(flag)cout<<"NO"<<endl;elsecout<<"YES"<<endl;}signedmain(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);intT;cin>>T;while(T--)slove();}

异或字典树

将每个二进制当作一个字符串s t r i n g \mathrm {string}string,构建一棵字符集为{ 0 , 1 } \mathrm {\{0,1\}}{0,1}的异或字典树。

最长异或路径

给定一棵n \mathrm nn个点的带权树,结点下标从1 \mathrm 11开始到n \mathrm nn。求树中所有异或路径的最大值。

异或路径指的是树上两个结点之间唯一路径上的所有边权的异或值。

解

找到一条路径( X , Y ) \mathrm {(X,Y)}(X,Y)使得路径异或值最大,而路径( X , Y ) \mathrm {(X,Y)}(X,Y)的异或值这其实等价于X \mathrm {X}X到L C A ( X , Y ) \mathrm {LCA(X,Y)}LCA(X,Y)的路径异或值异或值异或上Y \mathrm {Y}Y到L C A ( X , Y ) \mathrm {LCA(X,Y)}LCA(X,Y)的路径异或值。

进一步地等价于X \mathrm {X}X到R o o t \mathrm {Root}Root的路径异或值异或上Y \mathrm YY到R o o t \mathrm {Root}Root的路径异或值。

所以,我们其实只需要预处理出每个点到R o o t \mathrm {Root}Root的路径异或值,特别注意R o o t \mathrm {Root}Root到R o o t \mathrm {Root}Root自己的路径异或值是0 00。

所以我们现在的问题就变成,任选这n \mathrm nn个值(n \mathrm nn个路径异或值)中的两个,使得二者的异或之和最大。

把这n \mathrm nn个异或值用二进制方式存入字典树内,不妨令所有异或值均具有32 \mathrm {32}32位,我们从最高位开始存入字典树,树高是32 \mathrm {32}32。

将所有二进制存入字典树后,我们要想最终异或的结果最大,那么就要尽可能地让高位二进制位不同。

枚举每个异或值作为答案之一,另外一个异或值就需要贪心地从t i r e \mathrm {tire}tire树里面找。

代码

#include<bits/stdc++.h>usingnamespacestd;//#pragma GCC optimize(2)#defineintlonglong#defineendl'\n'#definePIIpair<int,int>#defineINF1e18constintN=1e5+7;inttrie[32*N][3];intcnt=1;voidadd(string&s){intst=1;for(inti=0;i<s.size();i++){if(!trie[st][s[i]-'0']){trie[st][s[i]-'0']=++cnt;}st=trie[st][s[i]-'0'];}}// 查找与给定字符串 s 异或后最大的字符串intquery(string&s){intans=0,st=1,deep=31;for(inti=0;i<s.size();i++){intf=s[i]-'0';if(trie[st][1^f]){ans+=(1ll<<deep);st=trie[st][1^f];}else{st=trie[st][f];}deep--;}returnans;}voidslove(){intn;cin>>n;vector<PII>g[n+1];for(inti=1;i<=n-1;i++){intu,v,w;cin>>u>>v>>w;g[u].emplace_back(v,w);}vector<int>dp(n+1,0);function<void(int,int)>dfs=[&](intu,intfa){for(auto[v,w]:g[u]){if(v==fa)continue;dp[v]=dp[u]^w;dfs(v,u);}};dfs(1,0);intans=0;for(inti=1;i<=n;i++){string s;for(intj=31;j>=0;j--){if(dp[i]&(1ll<<j))s+='1';elses+='0';}add(s);}for(inti=1;i<=n;i++){string s;for(intj=31;j>=0;j--){if(dp[i]&(1ll<<j))s+='1';elses+='0';}ans=max(ans,query(s));}cout<<ans<<endl;}signedmain(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 3:55:57

TinyMCE4粘贴word超链接自动解析域名

超级Word粘贴神器 - 专治CMS编辑头疼病 各位PHP老铁们好啊&#xff01;我是江苏某不知名码农小王&#xff0c;最近接了个企业官网改版项目&#xff0c;客户爸爸提了个"小"需求——要在后台直接粘贴Word还能保留所有样式&#xff01;这不&#xff0c;我带着解决方案和…

作者头像 李华
网站建设 2026/10/24 9:34:06

TinyMCE6处理微信公众号音频视频嵌入

【穷学生开发手记】99元预算搞定TinyMCE的Word图片转存全终端公式渲染&#xff0c;顺便恰饭搞接单群 一、需求拆解&#xff1a;穷学生的“既要又要还要” 作为江苏某软件学院大三“卷王”&#xff0c;最近在给自己的CMS新闻系统升级&#xff0c;需求如下&#xff1a; 核心功能…

作者头像 李华
网站建设 2026/10/24 9:34:06

当电机开始“唱歌“:NVH工程师的降噪日常

电机振动噪声NVH入门到精通 电磁 振动噪声模态 具有电磁解析机理振动噪声的步步深入&#xff0c;视频操作带你从入门到精通&#xff0c;适合在校学生学习和刚转化入职工程师学习。 Maxwell and Workbench某次测试中&#xff0c;一台新能源车的驱动电机在3000rpm时发出蜂鸣般的啸…

作者头像 李华
网站建设 2026/10/24 10:17:55

FPGA教程系列-Vivado Aurora 8B/10B 例程解读

FPGA教程系列-Vivado Aurora 8B&#xff0f;10B 例程解读1. 核心支撑层 (最重要&#xff0c;以后要抄作业的部分) 对应层级&#xff1a; aurora_module_i : aurora_8b10b_0_support 这是整个工程的心脏。如果你要在自己的项目里使用 Aurora&#xff0c;不要直接例化那个 ​.x…

作者头像 李华