字典树:又称单词查找树,trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高
https://www.acwing.com/problem/content/144/
题意:
给定N个字符串S1,S2…SNS1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1S1~SNSN中有多少个字符串是T的前缀
解析:
用一个数组对每个单词的末尾累计
ac:
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
int trie[MAXN][27];
int num[MAXN];
int pos;
void ins(char c[])
{
int rt=0;
int len=strlen(c);
for(int i=0;i<len;i++)
{
int id=c[i]-'a';
if(trie[rt][id]==0)
trie[rt][id]=++pos;
rt=trie[rt][id];
}
num[rt]++;
}
int getsum(char c[])
{
int rt=0;
int ans=0;
int len=strlen(c);
for(int i=0;i<len;i++)
{
int id=c[i]-'a';
if(trie[rt][id]==0)
return ans;
rt=trie[rt][id];
ans+=num[rt];
}
return ans;
}
char s[MAXN];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",s+1),ins(s+1);
while(m--)
{
scanf("%s",s+1);
printf("%d\n",getsum(s+1));
}
return 0;
}
The XOR Largest Pair
在给定的N个整数A1,A2……AN中选出两个进行xor运算,得到的结果最大是多少?
解析:
将数字转化为二进制数,从左往右优先选择同一位,不同的两个数
用字典树来实现
ac:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int trie[MAXN*30][3];
int pos=0;
void ins(int x)
{
int rt=0;
for(int i=31;i>=0;i--)
{
int y=(x>>i)&1;
if(trie[rt][y]==0)
trie[rt][y]=++pos;
rt=trie[rt][y];
}
}
int get(int x)
{
int rt=0,ans=0;
for(int i=31;i>=0;i--)
{
int y=(x>>i)&1;
int c=y^1;
if(trie[rt][c])
rt=trie[rt][c],ans=ans<<1|1;
else
rt=trie[rt][y],ans=ans<<1;
}
return ans;
}
int main()
{
int n,x,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
ans=max(ans,get(x));
ins(x);
}
printf("%d\n",ans);
return 0;
}
链接:https://vjudge.net/problem/HDU-6625
题意:
给定两个数组a[i],b[i],可以任意改变数组的顺序,使得数组的字典序最小,其中c[i]=a[i]^b[i].
解析:
建立两颗01字典树,同时在两颗字典树上搜索,尽可能的选择相同的路径走,优先走00,11,其次01,10.以这样方法贪心得到c[i]数组,然后对c[i]数组排序
ac:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
struct node
{
int trie[MAXN*30][3];
int num[MAXN*30];
int pos=0;
void ins(int x)
{
int rt=0;
for(int i=30;i>=0;i--)
{
int y=(x>>i)&1;
if(trie[rt][y]==0)
trie[rt][y]=++pos;
rt=trie[rt][y];
num[rt]++;
}
}
}A,B;
int a[MAXN],b[MAXN];
int c[MAXN];
void init()
{
for(int i=0;i<=A.pos;i++)
A.trie[i][1]=A.trie[i][0]=A.num[i]=0;
for(int i=0;i<=B.pos;i++)
B.trie[i][1]=B.trie[i][0]=B.num[i]=0;
A.pos=B.pos=0;
}
void solve(int n)
{
for(int i=1;i<=n;i++)
{
c[i]=0;
int nowA=0,nowB=0;
for(int j=30;j>=0;j--)
{
if(A.num[A.trie[nowA][0]]&&B.num[B.trie[nowB][0]])
{
nowA=A.trie[nowA][0],nowB=B.trie[nowB][0];
A.num[nowA]--;B.num[nowB]--;
}
else if(A.num[A.trie[nowA][1]]&&B.num[B.trie[nowB][1]])
{
nowA=A.trie[nowA][1],nowB=B.trie[nowB][1];
A.num[nowA]--;B.num[nowB]--;
}
else if(A.num[A.trie[nowA][1]]&&B.num[B.trie[nowB][0]])
{
nowA=A.trie[nowA][1],nowB=B.trie[nowB][0];
A.num[nowA]--;B.num[nowB]--;
c[i]+=(1<<j);
}
else if(A.num[A.trie[nowA][0]]&&B.num[B.trie[nowB][1]])
{
nowA=A.trie[nowA][0],nowB=B.trie[nowB][1];
A.num[nowA]--;B.num[nowB]--;
c[i]+=(1<<j);
}
}
cout<<c[i]<<endl;
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
init();
int n,x,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),A.ins(a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]),B.ins(b[i]);
solve(n);
sort(c+1,c+n+1);
for(int i=1;i<n;i++)
printf("%d ",c[i]);
printf("%d\n",c[n]);
}
return 0;
}
/*
1
2
11 10
10 9
*/
题意:
给定N个数字,然后对于M个询问的K,求N个数字与K异或后得到的新数列的mex,就是在数字集合中未出现过的最小的非负数
解析:
很多二进制关于异或求最值的问题,都可以转换为字典树来求
异或有一些性质:
结合律:a^(x^c)=(a^x)^c,所以我们不需要每个数都去异或
如果x!=y,x^k!=y^k 如果x^k!=y^k,x!=y
求mex(a[i]^k)等价与mex(不等于任何a[i]^k),即将不出现的值和k异或出最小的情况,注意30w要*2,30w并不是能异或出的最大情况
我们直接将(0~60w)的不出现在a[]的数加入01字典树中,然后求出k与这个字典树中异或出的最小值情况
ac:
#include<bits/stdc++.h>
#define MAXN 600005
using namespace std;
int trie[MAXN*25][3];
int pos=0;
int vis[MAXN];
void ins(int x)
{
int rt=0;
for(int i=19;i>=0;i--)
{
int c=(x>>i)&1;
if(trie[rt][c]==0)
trie[rt][c]=++pos;
rt=trie[rt][c];
}
}
int get(int x)
{
int ans=0;
int rt=0,d=-1;
for(int i=19;i>=0;i--)
{
int c=(x>>i)&1;
if(trie[rt][c])//尽量走相同的
rt=trie[rt][c];
else if(trie[rt][c^1])//走不同的
{
rt=trie[rt][c^1];
ans+=(1<<i);
}
else{//走不了,就跳出,然后后面每种情况都算上
d=i;
break;
}
}
for(int i=d;i>=0;i--)
{
int c=(x>>i)&1;
ans+=(1<<i);
}
return ans;
}
int main()
{
int n,m,x;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
vis[x]++;
}
for(int i=0;i<=600000;i++)
if(vis[i]==0)
ins(i);
int y=0;
while(m--)
{
scanf("%d",&x);
y=y^x; //异或运算有结合律
printf("%d\n",get(y));
}
return 0;
}
https://ac.nowcoder.com/acm/contest/1221/B
题意:
给定n个字符串,然后询问与x字符串字串想等的个数*len的和,不能算自己的子串
解析:
字典树直接处理,ins有3个功能,插入,删除,查询,因为不能算自己的子串,所以要先删除,再查询,然后再插回去
因为字串种类非常多,所以trie[x][26]的x会非常大,x要开大一点
ac:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int trie[MAXN*26][26];
int cnt[MAXN*26];
int tot=0;
int ins(char c[],int st,int len,int sign)
{
int now=0;
for(int i=st,j=0;j<len;j++,i++)
{
int id=c[i]-'a';
if(trie[now][id]==0)
trie[now][id]=++tot;
now=trie[now][id];
}
if(sign==1) //插入
cnt[now]++;
else if(sign==2)//删除
cnt[now]--;
else //查询
return cnt[now];
}
char s[50005][10];
int main()
{
int n,kk,q,x,len;
scanf("%d%d",&n,&kk);
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]);
for(int j=0;j<kk;j++){
for(int k=1;k+j<=kk;k++){
ins(s[i],j,k,1);
}
}
}
scanf("%d",&q);
while(q--)
{
scanf("%d%d",&x,&len);
for(int i=0;i+len<=kk;i++)
ins(s[x],i,len,2);
int ans=0;
for(int i=0;i+len<=kk;i++)
ans+=ins(s[x],i,len,3)*len;
for(int i=0;i+len<=kk;i++)
ins(s[x],i,len,1);
printf("%d\n",ans);
}
return 0;
}
/*
2 3
abc
abc
2
1 3
1 2
*/