字典树(Trie)
前言: 字典树用于解决 有多个长度短的字符串的问题 异或问题
处理每个电话号码时要输出NO有2种情况,一是某个已经处理的电话号码是它的前缀,二是它是某个已处理的电话号码的前缀,2种情况分别处理即可,注意trie[0]也需要清零.
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int t,n,trie[maxn][30],end[maxn],size;
char str[15];
bool insert(int v)
{
int now=0;
for(int i=0;str[i]!='\0';i++)
{
if(end[now]) return false;
int son=str[i]-48;
if(trie[now][son]==0)
{
memset(trie[++size],0,sizeof(trie[size]));
end[size]=0;
trie[now][son]=size;
}
now=trie[now][son];
}
end[now]=v;
for(int i=0;i<=9;i++)
if(trie[now][i]) return false;
return true;
}
void empty()
{
memset(end,0,sizeof(end));
size=0;
memset(trie[0],0,sizeof(trie[0]));
}
int main()
{
cin>>t;
for(int i=1;i<=t;i++)
{
cin>>n;
bool flag=false;
for(int j=1;j<=n;j++)
{
scanf("%s",str);
if(insert(1)==false)
{
flag=true;
}
}
if(flag) printf("YES\n");
else printf("NO\n");
empty();
}
return 0;
}
与第一题类似,若密码可以匹配信息,有2种情况,一是信息是密码的前缀,二是密码是信息的前缀.且此题需要统计个数,则建立2个数组tot和end.tot统计当前节点向下有多少个单词,用于计算是密码是信息前缀的信息数量;end统计在当前节点有多少个单词结束,用于计算信息是密码前缀的信息数量.
将密码在字典树中匹配,将沿途的tot累加为ans,若密码长度大于可匹配长度,则ans可直接输出.若密码长度小于可匹配长度,则输出ans-end[now]+tot[now],即加上密码是信息前缀的信息数量(+tot[now),因为在now节点结束的信息对tot有贡献,需减去(-end[now])
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int n,m,len,trie[maxn][30],tot[maxn],end[maxn],size;
int key[maxn],info[maxn];
inline int read()
{
int s=1,x=0;char c=getchar();
while(c>'9'||c<'0'){if(c=='-') s=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-48;c=getchar();}
return s*x;
}
void insert(int v,int len)
{
int now=0;
for(int i=1;i<=len;i++)
{
int son=key[i];
if(trie[now][son]==0)
{
memset(trie[++size],0,sizeof(trie[size]));
trie[now][son]=size;
}
now=trie[now][son];
tot[now]++;
}
end[now]++;
}
int query(int len)
{
int now=0,ans=0;
bool flag=false;
for(int i=1;i<=len;i++)
{
if(trie[now][info[i]]==0)
{
flag=true;
break;
}
now=trie[now][info[i]];
ans+=end[now];
}
if(flag==false) return ans-end[now]+tot[now];
else return ans;
}
int main()
{
m=read();
n=read();
for(int i=1;i<=m;i++)
{
len=read();
for(int j=1;j<=len;j++)
{
key[j]=read();
}
insert(1,len);
}
for(int i=1;i<=n;i++)
{
len=read();
for(int j=1;j<=len;j++)
{
info[j]=read();
}
printf("%d\n",query(len));
}
return 0;
}
将每个数按照二进制存进字典树,从最高位按"先走不同,再走相同"的贪心规则向下走.
/*
将每个数转为2进制,从高位开始建立trie树,按照
贪心原则向下走,同时记录走过位数,最后还原
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int n,t,ans,trie[maxn][30],end[maxn],size;
int pow_2[35];
int to2(int x,int *a)
{
int p=-1;
while(x)
{
a[++p]=x%2;
x/=2;
}
return p;
}
int insert(int *a,int len)
{
int now=0,ret=0,xor_now=0;
for(int i=30;i>=0;i--)//共31位,最大是2^31-1,满足题目要求
{
int son=a[i];
//下方操作只有在操作第一个数时不安全
if(trie[xor_now][!son])//和当前位相反的数是否有
{
ret+=pow_2[i];
xor_now=trie[xor_now][!son];
}
else
{
xor_now=trie[xor_now][son];
}
if(trie[now][son]==0)
{
++size;
end[size]=0;
trie[now][son]=size;
}
now=trie[now][son];
}
end[now]=1;
return ret;
}
int main()
{
//初始化打表
pow_2[0]=1;
for(int i=1;i<=30;i++)
{
pow_2[i]=pow_2[i-1]*2;
}
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>t;
int t_2[35];
memset(t_2,0,sizeof(t_2));
int now_ans=insert(t_2,to2(t,t_2));
if(now_ans>ans && i!=1) ans=now_ans;
}
cout<<ans;
return 0;
}
总结: 字典树大部分题目的核心操作是在添加单词时进行的,正确性涉及到字典树的特殊结构(添加一个单词,若与前面添加的单词有关系,则在此时就会被处理;若与后面添加的单词有关系,会在后面被处理).
字典树可很好的解决前缀/后缀问题
字典树可解决部分关于位运算操作的问题(将数转为01串,建立0-1 trie)