[学习笔记]Trie与可持久化Trie
额,来刚字符串了
一. 最平凡的 \(\operatorname{Trie}\)
长这样
可以发现,这棵字典树用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串。
举个例子, \(1\to 4\to 8\to 13\) 表示的就是字符串 \(\mathrm{cab}\)
当然我们也可以看做每个点(除根节点外)有一个点权,这样处理起来更方便
我不会告诉你是我懒得画图了(
问题来了:该如何建树?
显然地,用一个结构体存所有的内容
struct Trie{
int son[26];
bool tag;
}trie[WR];
int root,tot;
然后对于插入字符串,有如下代码:(默认字符串全都是小写字母)
bool insrt(char *str){
int pos=root;//先从根节点开始扫
for(int i=0;i<strlen(str);i++){//扫每一位
int val=str[i]-'a';//方便讨论,这样一个点最多有26个孩子
if(!trie[pos].son[val]) trie[pos].son[val]=++tot;//还没编号就编一个号
pos=trie[pos].son[val];//更新目前位置,向下走一层
}
int mark=trie[pos].tag;
trie[pos].tag=true;//打一个结尾标记方便判断
return mark;//考虑是否有重复的字符串
}
如果我们想知道一个字符串是否在 \(\operatorname{Trie}\) 里面出现过也是非常容易的:
bool query(char *str){
int pos=root;
for(int i=0;i<strlen(str);i++){
int val=str[i]-'a';
if(!trie[pos].son[val]) return false;//如果不存在这一位就返回false
pos=trie[pos].son[val];//否则继续向下递归
}
return trie[pos].tag;//看看这里是否有标记
}
考虑这道模板题
发现没有区别 \(\cdots\cdots\)
就直接一波硬莽完事
点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=3000010,INF=1099588621776,mod=1e9+7;
struct Trie{
int son[63];
bool tag;
}trie[WR];
char ch[WR];
int n,q;
int cnt[WR];
int root,tot;
int read(){
int s=0,w=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<3)+(s<<1)+ch-'0';
ch=getchar();
}
return s*w;
}
int getval(char c){
if(c>='A'&&c<='Z') return c-'A';
else if(c>='a'&&c<='z') return c-'a'+26;
else return c-'0'+52;
}
bool insrt(char *str){
int pos=root;
for(int i=0;i<strlen(str);i++){
int val=getval(str[i]);
if(!trie[pos].son[val]) trie[pos].son[val]=++tot;
pos=trie[pos].son[val];
cnt[pos]++;
}
int mark=trie[pos].tag;
trie[pos].tag=true;
return mark;
}
int query(char *str){
int pos=root;
for(int i=0;i<strlen(str);i++){
int val=getval(str[i]);
if(!trie[pos].son[val]) return 0;
pos=trie[pos].son[val];
}
return cnt[pos];
}
signed main(){
int T=read();
while(T--){
for(int i=0;i<=tot;i++) cnt[i]=0,trie[i].tag=false;
for(int i=0;i<=tot;i++){
for(int j=0;j<=62;j++){
trie[i].son[j]=0;
}
}
root=tot=0;
n=read(),q=read();
for(int i=1;i<=n;i++){
scanf("%s",ch);
insrt(ch);
}
for(int i=1;i<=q;i++){
scanf("%s",ch);
printf("%lld\n",query(ch));
}
}
return 0;
}
二. \(0/1\;\operatorname{Trie}\)
顾名思义,就是只有 \(0\) 和 \(1\) 的 \(\operatorname{Trie}\)
可以有效地解决异或值的一类问题
比如:
这时我们考虑用字典树解决问题:
把这些数字转化成二进制扔进字典树里
查询的时候看看能不能走 \(1\) (这样肯定比走 \(0\) 大)就完事了
点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define WR WinterRain
using namespace std;
const int WR=5001000;
int n,m,tot,ans;
int trie[WR][2],cnt[WR];
int read(){
int s=0,w=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+ch-'0';
ch=getchar();
}
return s*w;
}
void insert(int x) {
int p=0;
for(int i=31;i>=0;i--) {
int c=(x>>i)&1;
if(!trie[p][c]) trie[p][c]=++tot;
p=trie[p][c];
}
}
int search(int x) {
int p=0,ans=0;
for(int i=31;i>=0;i--) {
int c=(x>>i)&1,o=c^1;
if(trie[p][o]) p=trie[p][o],ans=ans<<1|1;
else p=trie[p][c],ans<<=1;
}
return ans;
}
int main(){
n=read();
for(int i=1;i<=n;i++) {
int x=read();
ans=max(ans,search(x));
insert(x);
}
printf("%d",ans);
return 0;
}
然后考虑这道题目
又因为异或满足分配律,因此 \(dis_{u,1}\oplus dis_{v,1}=dis_{u,\operatorname{LCA}}\oplus dis_{v,LCA}=dis_{u,v}\)
没了
点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=5001000;
struct Edge{
int pre,to,val;
}edge[WR>>2];
struct Trie{
int son[2];
}trie[WR];
int n,ans;
int head[WR>>2],cnt=0;
int ipt[WR>>2],dis[WR>>2];
int tot,root;
int read(){
int s=0,w=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<3)+(s<<1)+ch-48;
ch=getchar();
}
return s*w;
}
void insrt(int x){
int pos=root;
for(int i=31;i>=0;i--){
int val=((x>>i)&1);
if(!trie[pos].son[val]) trie[pos].son[val]=++tot;
pos=trie[pos].son[val];
}
}
void query(int x){
int res=0,pos=root;
for(int i=31;i>=0;i--){
int val=((x>>i)&1);
if(trie[pos].son[val^1]){
pos=trie[pos].son[val^1];
res|=(1<<i);
}else{
pos=trie[pos].son[val];
}
}
ans=max(ans,res);
}
void add(int u,int v,int val){
edge[++cnt].to=v;
edge[cnt].pre=head[u];
edge[cnt].val=val;
head[u]=cnt;
}
void dfs(int root,int u){
insrt(dis[u]);query(dis[u]);
for(int i=head[u];i;i=edge[i].pre){
int v=edge[i].to;
if(v==root) continue;
dis[v]=dis[u]^edge[i].val;
dfs(u,v);
}
}
signed main(){
n=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}
dfs(0,1);
printf("%lld\n",ans);
return 0;
}
三. 可持久化的 \(\operatorname{Trie}\)
如果您还不会主席树,请移步
好的,然后可持久化的 \(\operatorname{Trie}\) 就比较显然了
那么
我们从第 \(i\) 个根节点开始遍历,可以扫到第 \(1\sim i\) 次加入的字符串
一道比较经典的例题
直接维护前缀和建 \(\operatorname{Trie}\) 树即可
点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=20001000;
struct Trie{
int son[2];
}trie[WR];
int n,m;
int root[WR],rt,tot;
int sze[WR];
int read(){
int s=0,w=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<3)+(s<<1)+ch-48;
ch=getchar();
}
return s*w;
}
void insrt(int x){
int pos=root[rt];
root[++rt]=++tot;
for(int i=31;i>=0;i--){
int val=(x>>i)&1;
sze[tot]=sze[pos]+1;//让长度增加
trie[tot].son[val]=tot+1;
trie[tot].son[val^1]=trie[pos].son[val^1];//继承上一个节点的子树信息
pos=trie[pos].son[val];
tot++;
}
sze[tot]=sze[pos]+1;
}
int query(int l,int r,int x){
int lson=root[l],rson=root[r],res=0;
for(int i=31;i>=0;i--){
int val=(x>>i)&1;
if(sze[trie[rson].son[val^1]]-sze[trie[lson].son[val^1]]>0){
lson=trie[lson].son[val^1];
rson=trie[rson].son[val^1];
res|=(1<<i);
}else{
lson=trie[lson].son[val];
rson=trie[rson].son[val];
}
}
return res;
}
signed main(){
n=read(),m=read();
int sum=0;
insrt(0);
for(int i=1;i<=n;i++){
int x=read();
sum^=x;
insrt(sum);
}
while(m--){
char opt[10];
scanf("%s",opt);
if(opt[0]=='A'){
int x=read();
sum^=x;
insrt(sum);
}else{
int l=read(),r=read(),x=read();
printf("%lld\n",query(l-1,r,x^sum));
}
}
return 0;
}
本文来自博客园,作者:冬天丶的雨,转载请注明原文链接:https://www.cnblogs.com/WintersRain/p/16700430.html
为了一切不改变的理想,为了改变不理想的一切