引入
假如我们给出大量的字符串,查询其中某两串的公共前缀,显然不断枚举比较是否相同的办法对于多次询问太慢了,这就要用到Trie树。
Trie树即字典树,是一种能快速检索字符串的多叉树,但是Trie树的内存消耗很大,是用空间换时间的思想。那么它是如何实现的呢?
原理
假如给出abc,abd,abcd,bcd,efg五个字符串,先来看看构造出来的树。
我们发现根节点是没有字符;第一字符不同在不同的子树上,而abc,abd第一字符相同就在同一子树上;而abc和abd由于最后一个字符不同,所以又分支了出去。原理十分简单。
如果我们已经将一个字符串输完了,只需要将最后节点标记一下,下次遍历时只要到了该节点,那么搜到的所有点组合起来就是该字符串。
我们来详细地了解如何建立和查询一棵Trie树。
建立
我们开一个二维数组存储,trie[ i ][ j ],表示i节点的第j个子节点的编号为a,其中如果是小写字母那么第二维就可以是26,0~9的数字那么就是10。
我们再引入一个指针p来指向i这个节点,如果trie[p][i]==0,说明该字符还没建立,那就建立并把p移到该新节点上,如果trie[p][i]!=0那么就说明这里已经建了,就直接跳到它的子节点。
void Build()
{
int p=0;
for(int i=0;i<strlen(str);i++)
{
if(trie[p][str[i]-'a']==0)
{
trie[p][str[i]-'a']=++tot;
}
p=trie[p][str[i]-'a'];
}
}
查询
例如查询一个字符串,看它是否在字典树中。
首先第一个节点,如果得到的编号没有定义如等于0,那么说明不存在,如果第一个节点编号存在,那么就跳到它下面的一个编号,继续判定。如果全部枚举完了,并且最后一个节点有标记了是一个字符串,那么就找到了。
int Search()
{
int p=0;
for(int i=0;i<strlen(str);i++)
{
if(trie[p][str[i]-'a']==0) return 0;
p=trie[p][str[i]-'a'];
}
return 1;
}
板子传送门HDU 1251
#include<bits/stdc++.h>
using namespace std;
#define N 100005
int trie[N][26];
int ed[N];
int tot;
char str[15];
void Build()
{
int p=0;
for(int i=0;i<strlen(str);i++)
{
if(trie[p][str[i]-'a']==0)
{
trie[p][str[i]-'a']=++tot;
}
p=trie[p][str[i]-'a'];
ed[p]++; //记录到了目前节点有重复了,如abc,abcd,则a点ed为2,b点ed为2.c点ed为2,d点ed为1
}
}
int Search()
{
int p=0;
for(int i=0;i<strlen(str);i++)
{
if(trie[p][str[i]-'a']==0) return 0;
p=trie[p][str[i]-'a'];
}
return ed[p];
}
int main()
{
freopen("trie.in","r",stdin);
freopen("trie.out","w",stdout);
while(gets(str) && str[0]!='\0') Build();
while(scanf("%s",&str)!=EOF)
cout<<Search()<<endl;
return 0;
}
ed在for循环内统计每个节点重复次数,如果放到外面,就是统计每个字符串重复的次数。
PS:特别要注意读入的问题。
深入
最长异或路径luoguP4551
解决这道题,我们先看看这个问题:
在给定的N个整数A1,A2,……,AN中选出两个进行xor运算,得到的结果最大是多少?
xor表示二进制的异或(^)运算符号。
对于100%的数据: N≤105;0≤Ai<231。
既然是异或位运算,那么就联想到二进制,那么我们就把每个整数写成二进制数,由于A最大为231那我们就全部作成长度为30的01串,不足的加前导0。
实现转换为二进制:
for(int i=30;i>=0;i--)
{
int t=x>>i&1;
}
由于异或只有0^1=1,那么肯定A!和A2必须要从最高位开始尽量的每一位不相同。就有点像找公共前缀。那我们考虑使用字典树解决。
首先建树差不多从高到低建就可以了,重点是查询操作。
我们每处理建了一个01串,我们就去询问用这个01串来在当前建的字典树中找,不断更新答案,(那会不会本来就是用这个01串找,但这个是第一建树,就找不了呢?)不会,因为尽管它没找到,但它存到了字典树中,只要和它匹配的另一01串去更新就行了。
那该如何找?
从高到低取出每一位,如果是1,那就找1^1=0,如果是0,那就找0^1=1,如果不存在对应的数字,那就只能用相同的了。
#include<bits/stdc++.h>
using namespace std;
#define N 100005
int n,a,ans=0,tot;
int trie[N*31][2];//只有0和1两种
void Build(int x)
{
int p=0;
for(int i=30;i>=0;i--)
{
int t=x>>i&1;
if(trie[p][t]==0)
trie[p][t]=++tot;
p=trie[p][t];
}
}
int Search(int x)
{
int p=0,sum=0;
for(int i=30;i>=0;i--)
{
int t=x>>i&1;
if(trie[p][t^1]!=0)
{
p=trie[p][t^1];
sum+=1<<i;
}
else
{
p=trie[p][t];
}
}
return sum;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
Build(a);
ans=max(ans,Search(a));
}
cout<<ans;
return 0;
}
最长异或路径luoguP4551
有了上面的铺垫,我们来看这道题。
首先我们要知道
因为a^0=a,a^a=0,所以(a^b)^(b^c)=a^c
那么要求2个叶子节点的最大异或和,我们可以求根节点到每个叶子节点的异或和,要求2个叶子节点的异或和就等于两个到根的异或和的异或。
求根到每个叶子的很简单,直接dfs遍历即可,然后就把题转换为了根到哪2个叶子的异或和最大。
咦?这不就是我们上面做的题吗 ,直接ctrl+c/v。
我这里使用邻接表存储。
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m,ans,tot=1;
int f[N],first[N];
int trie[N*31][2];
struct edge //邻接表数据结构
{
int to,next,v;
}e[N*2]; //共有N-1条边,但是双向,所以数量翻倍
void build(int x) //构建Trie树
{
int p=0;
for(int k=30;k>=0;k--)
{
int t=x>>k&1;
if(trie[p][t]==0) trie[p][t]=++tot;
p=trie[p][t];
}
}
int query(int x) //Trie树查询
{
int p=0;
int tmp=0;
for(int k=30;k>=0;k--)
{
int t=x>>k&1;
if(trie[p][t^1])
{
p=trie[p][t^1];
tmp=tmp+(1<<k); //叠加新的异或结果:1;也可写成:tmp=tmp|(1<<k);
}
else
{
p=trie[p][t];
}
}
return tmp;
}
void insert(int u,int v,int w) //建立邻接表
{
e[++m].to=v;
e[m].next=first[u];
first[u]=m;
e[m].v=w;
}
void dfs(int x,int fa) //算出x所有子节点的f[i],x的父亲是fa
{
for(int i=first[x];i;i=e[i].next)
if(e[i].to!=fa) //避免往回搜,此处也可以用做标记的方式
{
f[e[i].to]=f[x]^e[i].v;
dfs(e[i].to,x);
}
}
int main()
{
//freopen("xorpath.in","r",stdin);
//freopen("xorpath.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<n;i++) //读入每条边并插入到邻接表
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
insert(u,v,w);
insert(v,u,w);
}
dfs(1,0); //算出根到每个节点的XOR路径长度值f[i]
for(int i=1;i<=n;i++)
{
build(f[i]); //Trie树中插入f[i]
ans=max(ans,query(f[i])); //更新最大值
}
printf("%d",ans);
return 0;
}
ok一道紫题就这么轻松的A了