Trie树(小)总结 By cellur925

关于\(Trie\)树的详细介绍,还请移步这篇深度好文

基本操作

插入

void insert()
{
	int p=0;
	int len=strlen(tmp+1);
	for(int i=1;i<=len;i++)
	{
		int qwq=tmp[i]-'0';
		if(!trie[p][qwq]) trie[p][qwq]=++tot;
		p=trie[p][qwq];
	}
}

注意,其中\(tot=0\),我习惯\(p\)初始值也为\(0\)\(tot\)\(p\)初值应保持一致。

检索操作非常灵活,各题不同,但是都很简单,那么就自己想吧=w=(不负责地逃离)。

几道例题

简单题1:Luogu P3879 [TJOI2010]阅读理解

在每个文章记录一下出现次数即可。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

int n,m,l,p,tot=1;
int trie[500090][26];
bool b[500000][1100];
char word[25];

void insert(char *str,int opt)
{
    int len=strlen(str);p=1;
    for(int k=0;k<len;k++)
    {
    	int ch=str[k]-'a';
    	if(!trie[p][ch]) trie[p][ch]=++tot;
    	p=trie[p][ch];
    }
    b[p][opt]=1;
}

void ask(char *str)
{
    int len=strlen(word);p=1;
    bool flag=1;
    for(int k=0;k<len;k++)
    {
        if(!trie[p][str[k]-'a'])
        {
            flag=0;break;
        }
        p=trie[p][str[k]-'a'];
    }
    if(flag)
    {
        for(int i=1;i<=n;i++)
            if(b[p][i])
                printf("%d ",i);
    }
} 

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&l);
        for(int j=1;j<=l;j++)
            scanf("%s",word),insert(word,i);
    }
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",word);
        ask(word);
        printf("\n");
    }
    return 0;
}

简单题2:UVA11362 Phone List

给定\(n\)个长度不超过\(10\)的数字串,判断是否有两个字符串\(A\)\(B\),满足\(A\)\(B\)的前缀。

满足题目中的条件对于每个串有两种情况:当这个串没读完的时候,有字符串以当前指针为结尾,说明之前串是当前串的前缀;这个串读完后,在计数数组中已经有记录,说明当前串是之前串的前缀。第一种情况开始被我忽略了。

分析完这点后,注意多组数据清空,空间开合适就好了。但是这题特别坑,存在的情况输出\(NO\),不存在输出\(YES\)。被坑了233.

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

int T,n,tot,lim;
int trie[200900][20],endd[200090],cnt[200090];
bool flag;
char tmp[20];

void in_trie()
{
	int p=0,len=strlen(tmp+1);
	for(int i=1;i<=len;i++)
	{
		int qwq=tmp[i]-'0';
		if(!trie[p][qwq]) trie[p][qwq]=++tot;
		p=trie[p][qwq];
		cnt[p]++;
		if(endd[p]) flag=1;
	}
	endd[p]=1;
	if(cnt[p]>1) flag=1;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		flag=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",tmp+1);
			in_trie();
		}
		if(flag) printf("NO\n");
		else printf("YES\n");
		memset(endd,0,sizeof(endd));
		memset(trie,0,sizeof(trie));
		memset(cnt,0,sizeof(cnt));
		tot=0;
	}
	return 0;
}

话说做这道题的时候开始用的数组叫\(end\)然后\(Compile\) \(error\)了...虚的一批。

简单题3:Luogu P2580 于是他错误的点名开始了

非常符合字典树的性质,直接上就行了。

#include<bits/stdc++.h>
using namespace std;
int n,m,tot,p;
int flag,end[1000090];
int trie[1000090][27];
char a[100];
void insert(char *str)
{
    int len=strlen(str);
    p=1;
    for(int k=0;k<len;k++)
    {
        int ch=str[k]-'a';
        if(trie[p][ch]==0) trie[p][ch]=++tot;
        p=trie[p][ch];
    }
    end[p]=1;
}
bool search(char *str)
{
    int len=strlen(str),p=1;
    for(int k=0;k<len;k++)
    {
        p=trie[p][str[k]-'a'];
        if(p==0) return false;
    }
    flag=p;
    return true;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",a);
        insert(a);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",a);
        if(!search(a))
        {
            printf("WRONG\n");
            continue;
        }
        else
        { 
            if(end[flag]==1)
            {
            	printf("OK\n");
            	end[flag]++;
            	continue;
            }
            printf("REPEAT\n");
        }
    }
    return 0;
}

巧妙运用:\(01trie\)

Luogu P4551 最长异或路径

给定一棵\(n\)个点的带权树,结点下标从\(1\)开始到\(N\)。在树中找两个结点,求最长的异或路径。

先考虑一个简化问题:给你一个数列,在其中任选两个数异或,求能获得的异或最大值。

考虑把\(32\)位的二进制\(01\)串强行塞到\(trie\)树上,这个树上的所有元素只可能为\(0\)\(1\)。那么我们可以对于每个元素,在字典树上进行检索,每次尝试访问与这个元素的这个位的相反的指针,若指针指向空,那么只能访问和这位相同的指针。因为异或运算时“相同得1不同得0”的,所以这样能保证最优。

回到我们原来的问题之前,我们看另一道题:Luogu P2420 让我们异或吧。求两点间路径的所有边权的异或值。设\(d[i]\)为根节点到当前节点路径上的异或值。那么对于\((x,y)\)答案就是\(d[x]\) \(xor\) \(d[y]\)。为什么不用考虑他们的\(lca\)以上的部分呢,以为他们相同,一异或就会变成0了==。

代码还是稍微给一下?(虽然和今天的内容关系不大)

#include<bits/stdc++.h>
using namespace std;
int n,m,tot;
int head[200009];
int xxor[200009],visit[200009];
struct node{
    int to,val,next;
}edge[200009];
queue<int>q;
void add(int x,int y,int z)
{
    edge[++tot].to=y;
    edge[tot].val=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
void bfs()
{
    //queue<int>q;
    q.push(1);
    visit[1]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=edge[i].next)
        {
            int y=edge[i].to;
            if(visit[y]) continue;
            visit[y]=1;
            xxor[y]=xxor[x]^edge[i].val;
            q.push(y);
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++)
    {
    	int u=0,v=0,w=0;
    	scanf("%d%d%d",&u,&v,&w);
    	add(u,v,w);
    	add(v,u,w);
    }
    bfs();
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int u=0,v=0,ans=0;
        scanf("%d%d",&u,&v);
        printf("%d\n",xxor[u]^xxor[v]);
    }
    return 0;
}

回到我们最开始的问题==!有了前面两题的基础,那么我们在这个问题中要求的答案就等效于简化问题,只是数列变成了\(d[]\)

#include<cstdio>
#include<algorithm>

using namespace std;

int n,tot,ans;
int d[100090],head[100090],trie[100090*32][5];
struct node{
	int to,next,val;
}edge[200090];

void add(int x,int y,int z)
{
	edge[++tot].to=y;
	edge[tot].next=head[x];
	head[x]=tot;
	edge[tot].val=z;
}

void dfs(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		d[v]=d[u]^edge[i].val;
		dfs(v,u);
	}
}

void insert(int x)
{
	int p=0;
	for(int i=(1<<30);i;i>>=1)
	{
		bool qwq=x&i;//注意用bool 
		if(!trie[p][qwq]) trie[p][qwq]=++tot;
		p=trie[p][qwq];
	}
}

int ask(int x)
{
	int p=0,orz=0;
	for(int i=(1<<30);i;i>>=1)
	{
		bool qwq=x&i;
		if(trie[p][qwq^1]) orz+=i,p=trie[p][qwq^1];
		else p=trie[p][qwq];
	}
	return orz;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++)
	{
		int x=0,y=0,z=0;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	dfs(1,0);tot=0;
	for(int i=1;i<=n;i++) insert(d[i]);
	for(int i=1;i<=n;i++) ans=max(ans,ask(d[i]));
	printf("%d\n",ans);
	return 0;
}

一定要注意\(qwq\)\(bool\)类型的!这样保证只有0或1。如果是\(int\)的话,就不保证了!!(\(Warning\)

最后的最后

讨论下开空间的问题吧!

本蒟蒻几乎是做一道\(trie\)\(RE\)一次...(逃)

比较玄学...尽量开大一点好(逃)

posted @ 2018-10-31 17:50  cellur925&Chemist  阅读(309)  评论(0编辑  收藏  举报