「
「
「字符串算法
」
」
」第
4
4
4章 字典树
目录:
A.前缀统计
B.最大异或对
C.最长异或路径
D.阅读理解
A . A. A. 例题 1 1 1 前缀统计

分析:
一道 T r i e Trie Trie树模板题 需要支持插入 ( i n s e r t ) (insert) (insert)和查询 ( q u e r y ) (query) (query)两个操作就好了
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
struct Trie{
int son[305],end;
char ch;
}trie[N];
int n,m,lenx,p,tot,ret;
char x[N];
void insert(char a[]) //往字典树加入一个字符串
{
lenx=strlen(a);
p=0;
for(int i=0;i<lenx;i++)
{
if(!trie[p].son[x[i]])
{
trie[p].son[x[i]]=++tot;
trie[p].ch=x[i];
}
p=trie[p].son[x[i]];
}
trie[p].end++;
}
int search(char a[]) //查询
{
lenx=strlen(a);
p=0;ret=0;
for(int i=0;i<lenx;i++)
{
if(!trie[p].son[x[i]]) return ret;
p=trie[p].son[x[i]];
ret+=trie[p].end;
}
return ret;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%s",x);
insert(x);
}
for(int i=1;i<=m;i++)
{
scanf("%s",x);
printf("%d\n",search(x));
}
return 0;
}
B . B. B. 例题 2 2 2 最大异或对

分析:
第一眼看可以暴力枚举 然后把优化开到极致还是会
T
L
E
TLE
TLE
1040
m
s
(
1040ms(
1040ms(其实再卡卡就过了
)
)
)
这样看到
x
o
r
xor
xor 我们就可以想到
t
r
i
e
trie
trie 然后要考虑怎么操作
我们先想想异或是干嘛的 相同为
0
0
0 不同才为
1
1
1
想让这个
x
o
r
xor
xor值最大 那么
1
1
1肯定要在最高位才是最大 也就是选的数高位尽可能不同
这时候就可以用
t
r
i
e
trie
trie树了 让一个点有两个儿子 代表下一位是
0
0
0或
1
1
1
再想想根节点 应该从最高位开始 因为越接近根节点优先级越高 那就是越高位
然后正常建树 查询就要从高位到低位 有不同的就选不同的 这样才是
1
1
1 不行就继续往后查就好
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
struct Trie{
int son[3],x;
} trie[32*N];
int n,a[N],ans,p,qwq[32],tot,k;
void insert()
{
k=0;
for(int i=31;i>=0;i--){ //根节点是最高位
if (!trie[k].son[qwq[i]])
trie[k].son[qwq[i]]=++tot; //建树
k=trie[k].son[qwq[i]];
}
}
void find(){
k=0;
int ret=0;
for(int i=31;i>=0;i--)
{
if(trie[k].son[qwq[i]^1]==0) //有跟它这位相同
{
if(trie[k].son[qwq[i]]) //只有这位相同
k=trie[k].son[qwq[i]]; //跳过往后查
else
break; //说明后面都相同 就没有贡献了
}else{ //这一位不同
ret+=1<<i; //加上贡献
k=trie[k].son[qwq[i]^1];
}
}
ans=max(ans,ret);
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
p=a[i];
int t=0;
memset(qwq,0,sizeof(qwq));
while(p)
{
if(p&1)
qwq[t++]=1;
else
qwq[t++]=0;
p>>=1;
}
find();
insert();
}
printf("%d",ans);
return 0;
}
C . C. C. 例题 3 3 3 最长异或路径
分析:
h
h
h
hhh
hhh又是一道紫题
我们先把数变成二进制 存进
t
r
i
e
trie
trie 节省空间
然后可以发现 一个数
x
o
r
xor
xor另一个数 如
a
a
a^
b
b
b 设我们得到
a
a
a ^
b
=
c
b=c
b=c 我们会发现
c
c
c ^
b
b
b又会
=
a
=a
=a
也就是一个数
x
o
r
xor
xor两次同一个数 值不变
比如
7
7
7 ^
4
4
4
=
=
=
0111
0111
0111 ^
0100
0100
0100
=
=
=
0011
0011
0011
=
=
=
3
3
3
3
3
3 ^
4
4
4
=
=
=
0011
0011
0011 ^
0100
0100
0100
=
=
=
0111
0111
0111
=
=
=
7
7
7 也就是值不变
这样来 路径
[
x
,
y
]
[x,y]
[x,y]上的异或和 就可以变成
[
r
o
o
t
,
x
]
[root,x]
[root,x]的异或和 异或上
[
r
o
o
t
,
y
]
[root,y]
[root,y]的异或和
就是连续
x
o
r
xor
xor了两次
r
o
o
t
(
root(
root(根节点
)
)
)
这样一来 后面我们做个贪心就可以了 就是如果这一位异或后是
1
(
1(
1(不同
)
)
) 就沿着这条路径往下走 不然就原路往下走
贪心策略证明:
如果当前是
1
1
1 那么它的权值比它后面任何值的总和都大 所以我们直接选这一位就可以了
比如一个二进制数是
10000000
10000000
10000000 无论如何 它也是比
01111111
01111111
01111111大的
(
(
(差不多是 最高位决定一切 的意思
)
)
)
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+3;
struct node{
int u,v,next;
}edge[2*N];
int head[2*N],sum[2*N],n,tot,ans,tot2=-1;
struct Trie{
int num[2];
}trie[2*N];
void add(int u,int v,int w)
{
edge[++tot2]=(node){w,v,head[u]};
head[u]=tot2;
}
void dfs(int dep,int fa)
{
for(int i=head[dep];~i;i=edge[i].next)
{
int u=edge[i].u,v=edge[i].v;
if(v==fa) continue;
sum[v]=sum[dep]^u;
dfs(v,dep);
}
}
void build(int val)
{
int dep=0;
for(int i=(1<<30);i;i>>=1) //从最高位到最低位
{
bool place=val&i; //取出这个数的位置
if(!trie[dep].num[place]) trie[dep].num[place]=++tot;
dep=trie[dep].num[place];
}
}
int query(int x)
{
int ret=0,dep=0;
for(int i=(1<<30);i;i>>=1)
{
bool place=x&i;
if(trie[dep].num[!place]) //如果可以异或
{
ret+=i;
dep=trie[dep].num[!place]; //沿着走
}
else dep=trie[dep].num[place]; //不然就原路
}
return ret;
}
int main(){
scanf("%d",&n);
memset(head,-1,sizeof(head));
for(int i=1;i<n;i++)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
add(x,y,w);
add(y,x,w);
}
dfs(1,-1);
for(reg int i=1;i<=n;i++)
build(sum[i]);
for(reg int i=1;i<=n;i++)
ans=max(ans,query(sum[i]));
printf("%d",ans);
return 0;
}
D . D. D. 例题 4 4 4 阅读理解
分析:
较为模板的
t
r
i
e
trie
trie
要注意输出那里 注意换行的位置 然后就是存单词进
t
r
i
e
trie
trie树 然后给标记来查询就好了
但是这个数据比较恶心 你数组开大了就全
M
L
E
MLE
MLE了 开小了就剩最后一个点
W
A
WA
WA
开的差不多大还会这样:

就很搞人心态 不过这仅限洛谷数据
h
h
h
h
hhhh
hhhh 一本通
O
J
OJ
OJ的数据还是很水的
然后把标记数组开个
b
i
t
s
e
t
bitset
bitset就可以了
S
T
L
STL
STL果然优秀 节省
32
32
32倍空间 效率还更高
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e4+10;
char s[1005];
struct Trie{
int son[26];
bitset<1011> end; //hhhh
}trie[30*N];
int n,tot,m,lens;
void insert(char a[],int x)
{
int now=0;
for(int i=1;i<=lens;i++)
{
int p=a[i]-'a';
if(!trie[now].son[p])
trie[now].son[p]=++tot; //存单词
now=trie[now].son[p];
}
trie[now].end[x]=1; //标记出现的单词
}
void solve(char a[])
{
int now=0,f=1;
for(int i=1;i<=lens;i++)
{
int p=a[i]-'a';
if(!trie[now].son[p]){f=0;break;}
now=trie[now].son[p];
}
if(f)
for(int i=1;i<=n;i++)
if(trie[now].end[i]) //有出现就输出
printf("%d ",i);
printf("\n");
}
int main(){
scanf("%d",&n);
for(reg int i=1;i<=n;i++)
{
scanf("%d",&m);
for(reg int j=1;j<=m;j++)
{
scanf("%s",s+1);
lens=strlen(s+1);
insert(s,i);
}
}
int q;
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%s",s+1);
lens=strlen(s+1);
solve(s);
}
return 0;
}
本文详细介绍了字典树(Trie树)在字符串算法中的应用,包括前缀统计、最大异或对、最长异或路径等例题的分析和解题思路。通过贪心策略证明了在某些问题中Trie树的优势,并提供了相关代码实现。




561

被折叠的 条评论
为什么被折叠?



