滑蒻稽的博客

字典树(Trie)

前言: 字典树用于解决 有多个长度短的字符串的问题 异或问题


#2122 【模板】电话簿

处理每个电话号码时要输出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;
}

#2204 秘密消息

与第一题类似,若密码可以匹配信息,有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;
}

#3827 The XOR Largest Pair

将每个数按照二进制存进字典树,从最高位按"先走不同,再走相同"的贪心规则向下走.

/*
将每个数转为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)

posted @ 2021-02-02 21:51  huaruoji  阅读(124)  评论(0编辑  收藏  举报