zoukankan      html  css  js  c++  java
  • 字典树(Trie)的学习笔记

    按照一本通往下学,学到吐血了。。。

    例题1

    字典树模板题吗。

    先讲讲字典树:

    在这里插入图片描述

    给出代码(太简单了。。。)!

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    struct  trie
    {
        int  a[10],v;
        //a代表0~9的儿子的编号,有则为这个节点的编号,没有就是0。
        //v是附加权值,代表每个节点被经过的次数。
    }tr[200000];int  trlen;//多少节点
    char  st[11000][20];int  n;
    void  add(char  ss[])
    {
        int  len=strlen(ss+1),root=0;//root代表根节点
        for(int  i=1;i<=len;i++)
        {
            int  k=ss[i]-'0';//计算是哪个儿子。。。
            if(tr[root].a[k]==0)tr[root].a[k]=++trlen;//添加新节点
            root=tr[root].a[k];tr[root].v++;//给这个节点加一次走过的标记
        }
    }
    int  find(char  ss[])
    {
        int  len=strlen(ss+1),root=0;//root代表根节点
        for(int  i=1;i<=len;i++)//找到这个字符串最底下的节点编号
        {
            int  k=ss[i]-'0';
            root=tr[root].a[k];
        }
        return  tr[root].v-1;//被经过两次代表这个字符串是某个字符串的前缀。
    }
    int  main()
    {
        int  T;scanf("%d",&T);
        while(T--)
        {
            memset(tr,0,sizeof(tr));trlen=0;//初始化
            scanf("%d",&n);
            for(int  i=1;i<=n;i++)//添加
            {
                scanf("%s",st[i]+1);
                add(st[i]);
            }
            bool  bk=false;
            for(int  i=1;i<=n;i++)
            {
                if(find(st[i]))//发现答案
                {
                    bk=true;
                    printf("NO
    ");//输出
                    break;
                }
            }
            if(bk==false)printf("YES
    ");//同样是输出。
        }
        return  0;
    }
    

    例题二

    我们发现只要把每个数字转成31位二进制,然后,一个个插入字典树,在插入之前,我们计算一下当前的数与哪个数的异或值最大,并记录一下。
    至于如何找,只需要,我们只需要每次走与当前二进制位数不一样的数字就行了,看代码还挺好理解的(find函数)

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    struct  node
    {
        int  a[2],v;//v是附加权值,代表这个二进制数是多少
    }tr[6100000];int  trlen;
    char  st[40];
    int  n,ans;
    void  lintoto(int  x)//将x转成二进制数
    {
    	memset(st,0,sizeof(st));
        int  len=0;
        while(x)
        {
            st[++len]=(x&1);//相当于x%2。
            x>>=1;//除于2
        }
    }
    void  add(char  ss[],int  id)
    {
        int  len=31,root=0;
        for(int  i=len;i>=1;i--)//由于当时存二进制是倒着存的,现在也要倒着搜
        {
            int  k=ss[i];
            if(!tr[root].a[k])tr[root].a[k]=++trlen;//新建节点
            root=tr[root].a[k];
        }
        tr[root].v=id;//添加附加权值
    }
    inline  int  mymax(int  x,int  y){return  x>y?x:y;}
    inline  int  find(char  ss[],int  id)
    {
        int  root=0,len=31;
        for(int  i=len;i>=1;i--)
        {
            int  k=ss[i];
            if(tr[root].a[k^1])root=tr[root].a[k^1];//走相反的地方
            else  root=tr[root].a[k];//不存在,走相同的地方。
        }
        return  (tr[root].v^id);
    }
    int  main()
    {
        scanf("%d",&n);
        for(int  i=1;i<=n;i++)
        {
            int  x;scanf("%d",&x);
            lintoto(x);//转成二进制
            if(i!=1)ans=mymax(ans,find(st,x));//更新新的答案
            add(st,x);//添加
        }
        printf("%d
    ",ans);
        return  0;
    }
    

    例题三

    先证明异或符合交换律、结合律。

    首先,异或的过程中:(1,0),(0,1),(0,0)都是消掉一个0,而(1,1)是消掉一个1。

    我们可以发现,多次异或就是看所有数二进制的每一位的1的个数,奇数就为1,偶数为0,而你调换顺序的话,个数并没有变,所以满足交换律与结合律。

    那么这道题,我们设(sst_{i}=a_{1}⨁a_{2}⨁a_{3}⨁a_{4}...⨁a_{i}),那么,求区间最大,我们可以发现:

    (a_{l}⨁a_{l+1}⨁...⨁a_{r}=(a_{1}⨁a_{2}⨁a_{3}⨁...⨁a_{r})⨁(a_{1}⨁a_{2}⨁a_{3}⨁...⨁a_{l-1})=sst_{r}⨁sst_{l-1})
    也就是说,我们只要求出在1~r-1区间内能异或(sst_{r})的最大值,也就是上面那道题,设(ll_{i}=) 在1~i区间内的一个区间最大异或值,(ll_{i}=max(ll_{i-1},以i为结尾的最大异或区间))

    再设一个(rr_{i}=) 在i~n区间内的一个区间最大异或值,(rr_{i}=max(rr_{i+1},以i为开头的最大异或区间))

    求出最大的(ll_{i-1}+rr_{i})

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    struct  trie
    {
        int  a[2],v;
    }tr[9100000];int  trlen;//字典树
    char  st[210];
    int  sst[410000],n,ll[410000],rr[410000],ans;//数组
    void  intto(int  x)//像上次那样。。。
    {
        memset(st,0,sizeof(st));
        int  len=0;
        while(x)
        {
            st[++len]=(x&1);//x%2
            x>>=1;//x/=2
        }
    }
    void  add(char  ss[],int  id)//添加
    {
        int  len=31,root=0;
        for(int  i=len;i>=1;i--)
        {
            int  k=ss[i];
            if(!tr[root].a[k])tr[root].a[k]=++trlen;
            root=tr[root].a[k];
        }
        tr[root].v=id;
    }
    int  find(char  ss[],int  id)//寻找
    {
        int  len=31,root=0;
        for(int  i=len;i>=1;i--)
        {
            int  k=ss[i];
            if(tr[root].a[k^1])root=tr[root].a[k^1];
            else  root=tr[root].a[k];
        }
        return  (tr[root].v^id);//返回异或值
    }
    inline  int  getsum(int  x,int  y){return  sst[y]^sst[x-1];}//[x,y]区间的异或值
    inline  int  mymax(int  x,int  y){return  x>y?x:y;}//最大值
    int  main()
    {
        scanf("%d",&n);
        intto(0);
        add(st,0);//将0添加
        for(int  i=1;i<=n;i++)
        {
            scanf("%d",&sst[i]);sst[i]^=sst[i-1];
            intto(sst[i]);//二进制
            ll[i]=mymax(find(st,sst[i]),ll[i-1]);//更新
            add(st,sst[i]);//添加
        }
        memset(tr,0,sizeof(tr));trlen=0;
        intto(0);
        add(st,0);
        for(int  i=n;i>=1;i--)
        {
            intto(getsum(i,n));
            rr[i]=mymax(rr[i-1],find(st,getsum(i,n)));
            add(st,getsum(i,n));
        }//反着来一遍
        for(int  i=2;i<=n;i++)ans=mymax(ans,ll[i-1]+rr[i]);//统计答案
        printf("%d
    ",ans);
        return  0;
    }
    

    练习一

    跟例题一 一样。。。只不过处理方式出了点问题。

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    using  namespace  std;
    struct  node
    {
        int  a[10],v;
    }tr[2100];int  trlen;//字典树
    char  st[20];
    void  add(char  ss[])//打得不能再顺手的添加。。。
    {
        int  len=strlen(ss+1),root=0;
        for(int  i=1;i<=len;i++)
        {
            int  k=ss[i]-'0';
            if(!tr[root].a[k])tr[root].a[k]=++trlen;
            root=tr[root].a[k];
        }
        tr[root].v=1;
    }
    bool  find(char  ss[])//每天都在变。。。
    {
        int  len=strlen(ss+1),root=0;
        for(int  i=1;i<=len;i++)
        {
            int  k=ss[i]-'0';
            if(!tr[root].a[k])return  false;//不存在节点?返回
            root=tr[root].a[k];
            if(tr[root].v==1)return  true;//存在,返回
    	}
        return  false;
    }
    int  main()
    {
        int  T=0;
        while(scanf("%s",st+1)!=EOF)
        {
            T++;
            memset(tr,0,sizeof(tr));trlen=0;//初始化
            bool  bk=false;
            while(1)
            {
                int  len=strlen(st+1);
                if(len==1  &&  st[1]=='9')break;//退出
                if(find(st))bk=true;//寻找
                add(st);//添加
                scanf("%s",st+1);//输入
            }
            if(bk==true)printf("Set %d is not immediately decodable
    ",T);
            else  printf("Set %d is immediately decodable
    ",T);
        }
        return  0;
    }
    

    练习二

    将字典建个字典树,然后类似递推思想瞎搞。。。

    提示:建一个d数组代表这一段在前i位能否被表达。

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    struct  node
    {
        int  a[26],v;
    }tr[51000];int  trlen;//字典树
    char  st[2100000];//字符串
    int  n,m;
    char  d[2100000];//d数组
    void  add(char  ss[])
    {
        int  len=strlen(st+1),root=0;
        for(int  i=1;i<=len;i++)
        {
            int  k=st[i]-'a';
            if(!tr[root].a[k])tr[root].a[k]=++trlen;
            root=tr[root].a[k];
        }
        tr[root].v=1;
    }
    bool  find(int  l,int  r)//匹配l~r区间
    {
        int  root=0;
        for(int  i=l;i<=r;i++)
        {
            int  k=st[i]-'a';
            if(!tr[root].a[k])return  false;
            root=tr[root].a[k];
        }
        return  tr[root].v;
    }
    inline  int  mymax(int  x,int  y){return  x>y?x:y;}
    int  jie;
    int  main()
    {
        scanf("%d%d",&n,&m);
        for(int  i=1;i<=n;i++)
        {
            scanf("%s",st+1);
            add(st);
            jie=mymax(jie,strlen(st+1));//最长的单词
        }
        for(int  kkk=1;kkk<=m;kkk++)
        {
            memset(d,0,sizeof(d));d[0]=1;//初始化
            scanf("%s",st+1);
            int  len=strlen(st+1);
            for(int  i=1;i<=len;i++)
            {
                for(int  j=mymax(1,i-jie+1);j<=i;j++)
                {
                    if(d[j-1]==1  &&  find(j,i))//如果前j-1位已经匹配成功,那么判断当前能否匹配成功
                    {
                        d[i]=1;
                        break;
                    }
                }
            }
            int  ans=0;
            for(int  i=len;i>=1;i--)//统计
            {
                if(d[i]==1)
                {
                    ans=i;break;
                }
            }
            printf("%d
    ",ans);
        }
        return  0;
    }
    

    练习三

    建立信息为字典树,两个附加权值,代表被经过的次数和作为结尾的次数。

    那密码进行统计。。。

    怎么统计自己想或看代码

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    struct  trie
    {
        int  a[2],v1,v2;//两个附加权值,v1代表被经过次数,v2代表被作为结尾的次数
    }tr[610000];int  trlen;//字典树
    char  st[510000];//字符串
    int  n,m;
    void  add(int  len)//添加
    {
        int  root=0;
        for(int  i=1;i<=len;i++)
        {
            int  k=st[i];
            if(!tr[root].a[k])tr[root].a[k]=++trlen;
            root=tr[root].a[k];tr[root].v1++;//经过一次
        }
        tr[root].v2++;//作为结尾加一
    }
    int  find(int  len)
    {
        int  root=0,ans=0;
        for(int  i=1;i<=len;i++)
        {
            int  k=st[i];
            if(!tr[root].a[k])return  ans;
            root=tr[root].a[k];ans+=tr[root].v2;//有多少是他的前缀
        }
        ans+=tr[root].v1-tr[root].v2;//他是多少字符串的后缀
        return  ans;
    }
    int  main()
    {
        scanf("%d%d",&n,&m);
        for(int  i=1;i<=n;i++)
        {
            int  len;scanf("%d",&len);
            for(int  j=1;j<=len;j++)scanf("%d",&st[j]);//输入字符串
            add(len);//添加
        }
        for(int  i=1;i<=m;i++)
        {
            int  len;scanf("%d",&len);
            for(int  j=1;j<=len;j++)scanf("%d",&st[j]);//输入字符串
            printf("%d
    ",find(len));//寻找
        }
        return  0;
    }
    

    练习四

    这道题目QAQ。

    首先,我们知道,长度为s1的字符串a1,长度为s2的字符串a2,如果(s1<s2)并且a1与a2都是a3的后缀,那么a1是a2的后缀。

    将所有字符串翻转,建一个字典树,然后建一颗树,当(i)->(j)仅当,(a_{i})(a_{j})的最大前缀。

    然后,DFS新树,每次进入没进过且节点最少的树就行了。

    (如果直接在字典树上跑的话,子树大小会错乱,导致每次进的不是节点最小的树)

    这道题还是有点难的,因为用到了一点贪心思想。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using  namespace  std;
    char  st[520000];
    int  n;
    bool  bol[110000];
    struct  trie//字典树
    {
        int  a[26],v;
    }tr1[610000];int  trlen1;
    struct  node//新建的树
    {
        int  y,next;
    }tr2[110000];int  trlen2,last[110000],size[110000];//用边目录储存
    void  ins(int  x,int  y)//新建一条边
    {
        trlen2++;
        tr2[trlen2].y=y;tr2[trlen2].next=last[x];last[x]=trlen2;
    }
    void  add(int  id)//添加
    {
        int  root=0,len=strlen(st+1);
        for(int  i=len;i>=1;i--)//字符串翻转
        {
            int  k=st[i]-'a';
            if(!tr1[root].a[k])tr1[root].a[k]=++trlen1;
            root=tr1[root].a[k];
        }
        tr1[root].v=id;
    }
    int  dfss(int  x,int  fa)//遍历字典树,建新树,fa是离他最近的有权的祖先
    {
        int  ans=0;//代表子树大小
        for(int  i=0;i<=25;i++)
        {
            if(tr1[x].a[i])
            {
                if(tr1[x].v)ans+=dfss(tr1[x].a[i],tr1[x].v);//本身是一个节点
                else  ans+=dfss(tr1[x].a[i],fa);//本身不是一个节点,只是一个中转站
            }
        }
        if(tr1[x].v)//本身是一个节点
        {
            ins(fa,tr1[x].v);//连向父亲
            ans++;size[tr1[x].v]=ans;//继承大小
        }
        return  ans;//返回目前的子树大小
    }
    long  long  anss;
    int  times,lis[110000],listlen;
    bool  cmp(int  x,int  y){return  size[x]<size[y];}
    void  dfs(int  x)
    {
    	int  now=++times;
        int  l=listlen+1,r;
        for(int  k=last[x];k;k=tr2[k].next)lis[++listlen]=tr2[k].y;//处理list
        r=listlen;
        if(l<=r)sort(lis+l,lis+1+r,cmp);//排序是排[l,r) 
        else  return  ;//没有子树 
        for(int  i=l;i<=r;i++)
        {
            anss+=(times+1)-now;//统计答案 
            dfs(lis[i]);//继续递归 
        }
    }
    int  main()
    {
        scanf("%d",&n);
        for(int  i=1;i<=n;i++)
        {
            scanf("%s",st+1);
            add(i);
        }//添加所有字符串
        dfss(0,0);//建树 
        dfs(0);//处理答案 
        printf("%lld
    ",anss);//输出 
        return  0;
    }
    

    练习五

    这道题,我一开始想得太复杂了。。。

    我一开始想到的是一条路径上,起点是l,终点是r,然后还涉及到求LCA,然后就十分复杂。。。

    然后膜了一波题解,发现自己就是个弱智。。。

    (sum[i]=(1->i))的权值异或值 ,那么一条路径上的异或权值和就是(sum[l])^(sum[j]),然后就是求一个数组中求与(sum[i])异或值最大的数,其实就是例题了,根本不用在树上进行特别麻烦的遍历,或者是过程很麻烦的树上递归,是不是很巧妙?好吧,我承认就是我没有想到然后就乱膜。。。

    #include<cstdio>
    #include<cstring>
    using  namespace  std;
    struct  node
    {
    	int  y,next,c;
    }a[210000];int  last[110000],alen;
    int  sum[110000];//边目录 
    void  ins(int  x,int  y,int  c)
    {
    	alen++;
    	a[alen].y=y;a[alen].c=c;a[alen].next=last[x];last[x]=alen;
    }
    void  dfs(int  x,int  fa)
    {
    	for(int  k=last[x];k;k=a[k].next)
    	{
    		int  y=a[k].y;
    		if(y!=fa)//不是他的父亲 
    		{
    			sum[y]=sum[x]^a[k].c;//处理sum数组 
    			dfs(y,x);//继续递归 
    		}
    	}
    }
    struct  trie
    {
    	int  a[2],v;
    }tr[4100000];int  trlen;//字典树 
    char  st[40];//储存二进制的数组。 
    void  intoo(int  x)
    {
    	memset(st,0,sizeof(st));//初始化 
    	int  len=0;
    	while(x)
    	{
    		st[++len]=(x&1);//x%2
    		x>>=1;//x/=2
    	}
    }
    void  add(int  id)//添加 
    {
    	int  len=31,root=0;
    	for(int  i=len;i>=1;i--)//添加二进制要注意的 
    	{
    		int   k=st[i];
    		if(!tr[root].a[k])tr[root].a[k]=++trlen;
    		root=tr[root].a[k];
    	}
    	tr[root].v=id;
    }
    int  find(int  id)
    {
    	if(trlen==0)return  0;//判断目前字典树是否有节点 
    	int  len=31,root=0;
    	for(int  i=len;i>=1;i--)
    	{
    		int  k=st[i];
    		if(tr[root].a[k^1])root=tr[root].a[k^1];//贪心找异或值最大 
    		else  root=tr[root].a[k];
    	}
    	return  (id^tr[root].v);//给出结果 
    }
    int  n;
    inline  int  mymax(int  x,int  y){return  x>y?x:y;}//找最大值 
    int  main()
    {
    	scanf("%d",&n);
    	for(int  i=1;i<n;i++)
    	{
    		int  x,y,c;scanf("%d%d%d",&x,&y,&c);
    		ins(x,y,c);ins(y,x,c);
    	}
    	dfs(1,0);//默认1为根,处理sum数组 
    	int  ans=0;
    	for(int  i=1;i<=n;i++)//例题的做法 
    	{
    		intoo(sum[i]);
    		ans=mymax(ans,find(sum[i]));
    		add(sum[i]);
    	}
    	printf("%d
    ",ans);
    	return  0;
    }
    

    终于写完了。。。

  • 相关阅读:
    三角函数都快忘光了
    Windows 10 LTSC 2019(1809) WSL 安装 CentOS 7
    随手写了个京东发票助手
    ASP 封装基本身份认证( HTTP Basic Authenticate)辅助类
    WebBrowser中打开新页面
    将QT窗口嵌入到WinForm窗口
    [摘录]如何按需前端显示指定的窗口
    实现TabControl 选项卡首个标签缩进的方法
    玩转时间操作
    Java 并发包中的高级同步工具
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/9971782.html
Copyright © 2011-2022 走看看