• 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("
    ");
        }
        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
    ");
    		else printf("YES
    ");
    		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
    ");
                continue;
            }
            else
            { 
                if(end[flag]==1)
                {
                	printf("OK
    ");
                	end[flag]++;
                	continue;
                }
                printf("REPEAT
    ");
            }
        }
        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
    ",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
    ",ans);
    	return 0;
    }
    

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

    最后的最后

    讨论下开空间的问题吧!

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

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

  • 相关阅读:
    本机mysql 5.7服务启动后停止,某些服务在未有其他应用程序使用时停止
    java数据结构 • 面向对象 • 异常 • 随机数·时间
    获取对象信息
    集合的运算律
    常用的逻辑符号
    未解决问题
    继承和多态
    访问权限
    类和实例
    第三方模块安装
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9884636.html
Copyright © 2020-2023  润新知