「Tire树」学习笔记
定义与基本求法
-
定义
又称字典树,用边表示字母,从根节点到树上某一节点路径形成一个字符串。
例如
-
基本求法
廷显然的,往树中存就行了,查询也是显然的,通过一道例题来理解吧:
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=5e5+10,P=1e9+7; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } char s[N]; int n,m,t[N][30],v[N],tot=1; void Tire(char s[]) { int r=1,l=strlen(s+1); for(int i=1;i<=l;i++) { int c=s[i]-'a'; if(!t[r][c]) t[r][c]=++tot; r=t[r][c]; } v[r]=1; } void ask(char s[]) { int r=1,l=strlen(s+1); for(int i=1;i<=l;i++) { int c=s[i]-'a'; r=t[r][c]; if(!r) break; } if(v[r]==1) { cout<<"OK"<<endl; v[r]=2; } else if(v[r]==2) cout<<"REPEAT"<<endl; else cout<<"WRONG"<<endl; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<=n;i++) cin>>s+1, Tire(s); read(m); for(int i=1;i<=m;i++) cin>>s+1, ask(s); } 数组存的现节点是编号,第一维存的是根节点编号,第二维是边权。查询时,遇到该字符对应编号为
,说明这个字符串不存在与字典树中。
例题
-
题面:
给定
个字符串,判断是否存在两个字符串 ,使 是 的前缀。 -
解法:
多测,
匹配——将每一组存到字典树中,同时查询是否有前缀等于之前的串即可。
可以定义一个新的数组
用于判断,判断字符串 时,只要出现 就说明有字符串与其匹配了,当然,存的时候,存完另 。结合代码理解。
-
代码如下:
#include<bits/stdc++.h> #define int unsigned long long #define endl '\n' using namespace std; const int N=1e6+10,P=1e9+7; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int T,n,t[N][20],tot=1; char s[N]; bool f[N]; void Tire(char s[]) { int p=1,l=strlen(s+1); for(int i=1;i<=l;i++) { int c=s[i]-'0'; if(!t[p][c]) t[p][c]=++tot; p=t[p][c]; } f[p]=1; } bool find(char s[]) { int p=1,l=strlen(s+1); for(int i=1;i<=l;i++) { int c=s[i]-'0'; if(!t[p][c]) return 0; p=t[p][c]; if(f[p]) return 1; } return 1; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(T); while(T--) { memset(f,0,sizeof(f)); memset(t,0,sizeof(t)); tot=1; bool ans=0; read(n); for(int i=1;i<=n;i++) { cin>>s+1; if(find(s)) ans=1; Tire(s); } if(!ans) cout<<"YES"<<endl; else cout<<"NO"<<endl; } }
定义与基本求法:
-
定义
字符集只有
的 数,主要用来解决有关异或值的问题。 -
基本求法:
异或有着安慰考虑的性质,每一位贡献是分开的,这与
树用不同深度存不同位定性质是吻合的。如果要最大化异或值,一定先最大化其最高位,如果用
树从高到低来做,正好吻合了这个贪心思想。根据例题来理解吧。
例题
-
题面:
给定
个整数 ,在其中选出两个进行异或运算,求可以得到的最大结果。 -
解法:
足够大,暴力不要想。将这
个数转换成二进制,存到 树里。再取这
个树,在 上跑一边,尽可能的向与其二进制位不同的方向。此处体现了贪心的思想,因为二进制下
始终成立,高位优一定全局优。 -
代码如下:
#include<bits/stdc++.h> #define int long long #define endl '\n' using namespace std; const int N=3e6+10,P=1e9+7; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=1; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } int n,a[N],tot,t[N][2],ans; void Tire(int x) { int p=0; for(int i=31;i>=0;i--) { int l=(x>>i)&1; if(!t[p][l]) t[p][l]=++tot; p=t[p][l]; } } int find(int x) { int p=0,sum=0; for(int i=31;i>=0;i--) { int l=(x>>i)&1; if(t[p][l^1]) p=t[p][l^1], sum=sum<<1|1;//二进制运算 else p=t[p][l],sum=sum<<1;//同上 } return sum; } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); for(int i=1;i<=n;i++) read(a[i]), Tire(a[i]); for(int i=1;i<=n;i++) ans=max(ans,find(a[i])); cout<<ans; } 位运算方向注意不要写反了。
-
题面:
给定一棵
个节点的带权树,求书上最长的异或和路径。 -
解法:
首先了解异或的一个重要性质——自反性:
所以一个元素,若对其进行了重复偶数次的重复,则视作没有异或。
∴
故此求出每个点到根节点的异或和
,将 存到 中,问题就转化为了上一道题。至于如何求每个节点到根节点的异或和,可以用
解决。
总结
对于
直接使用
而此处对其进行整理主要为了为后面的
由此看,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效