字典树总结(CSU - 1216 && HDU - 1251)

字典树真的好可爱啊~

字典树

又称单词查找树,Trie树,是一种树形结构,哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

性质:

1、根节点不包含字符,每条边代表一个字符

2、从根节点刀模一个节点,路径上经过的字符连接起来,为该节点对应的字符串

3、每个节点的所有子节点包含的字符都不相同

实现方法

       以保存由26个小写字母组成的字符串为例,先设出一个根节点(作为虚点),每个节点引申出26条分支,分别代表'a' 'b' 'c' 'd' ……'z',一直顺着走下去,最后到字符串结尾时,把最后那个节点的 val 值置为true,表示以上路径代表了这个字符串。

例题应用

1、求前缀单词数量

   题目链接:点我点我

题目描述:给你一堆单词,然后开始查询字符串s是多少个单词的前缀。

题目思路:

      字典树裸题。因为我们要判断字符串是不是单词前缀,所以可能这个字符串的长度只能到单词的中间某个部分,我们之前标记的 val 是在单词结束的地方的,改动一下在单词的每一个字符结束时都把这个位置的 val 置为true就好了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 500000

using namespace std;

int trie[maxn][26];
int tot;
int val[maxn];
char a[maxn];

void inserts(char a[])
{
    int root=0;
    int len=strlen(a);
    for(int i=0;i<len;i++)
    {
        int id=a[i]-'a';
        if(!trie[root][id])
        {
            trie[root][id]=++tot;//tot的意义:承载字符的节点数++,如果简单置1的话后面可能会重复
        }
        val[trie[root][id]]++;//val[]既可以选择++,也可以简单置1,只是起标记作用,也可以声明为bool类型
        root=trie[root][id];
    }
}

int searchs(char b[])
{
    int root=0;
    int ll=strlen(b);
    for(int i=0;i<ll;i++)
    {
        int ii=b[i]-'a';
        if(!trie[root][ii])//这一位不匹配了
        {
            return 0;
        }
        root=trie[root][ii];
    }
    return val[root];
}
int main(void)
{
    memset(trie,0,sizeof(trie));
    memset(val,0,sizeof(val));
    tot=0;
    while(gets(a)&&a[0]!='\0')
    {
        //if(a[0]=='\0')
            //break;
        inserts(a);
    }
    while(~scanf("%s",a))
    {
        printf("%d\n",searchs(a));
    }
    return 0;
}

2、求最大异或值(01字典树)

01字典树:

     和普通字典树其实没有什么区别,唯一不同的就是,它是把十进制数分解成二进制的01串,然后按位依次插入,也就是说,这棵字典树的每个节点都只有两条分支,分别代表0和1。

应用:01字典树适用于进行异或操作,尤其是求最大异或值问题。

           比如给你数字a,b,c,然后给出数字d,问你在a,b,c组成的集合里,谁跟d的异或值最大。

          这时候我们可以把a,b,c以二进制形式插入字典树里。对于数字d,我们也分解成二进制形式,然后从高位到低位在字典树里寻找与d的当前位异或值为1的数字,(因为我们既然想获得最大的异或值,肯定想让这两个数的高位异或完尽可能都是1);

现在问题转化为:如何简单而有效的判断异或完是否为1呢?这里有一个异或运算的反身性,

即假设a^b=c,那么c^b=a且c^a=b;根据这个性质,如果(当前位)idx^1的这个数肯定与idx异或后值为1;

          那么就很简单了,我们采用从最高位向下贪心查找的方法,从字典树的根节点开始向下找,如果存在idx^1的节点,我们就进入这个节点,否则进入idx节点。

例题:

具体还是在代码中体现:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define maxn 100005

using namespace std;

typedef long long ll;
ll a[maxn];
int trie[maxn][2];//节点编号 节点表示的值
int val[maxn];
int tot;

void inserts(ll x)
{
     int root=0;
     for(int i=32;i>=0;i--)
     {
          int idx=(x>>i)&1;//把x从高位到低位取出
          if(!trie[root][idx])
          {
               trie[root][idx]=++tot;
          }
          val[trie[root][idx]]=x;
          root=trie[root][idx];
     }
}
ll searchs(ll x)//x-要询问的这个数
{
     int root=0;
     for(int i=32;i>=0;i--)
     {
          int idx=(x>>i)&1;//依次取出x的最高位
          if(trie[root][idx^1])
               root=trie[root][idx^1];
          else
               root=trie[root][idx];
     }
     return val[root];
}
int main(void)
{
     int n;
     while(~scanf("%d",&n))
     {
          tot=0;
          ll maxx=-1;
          memset(trie,0,sizeof(trie));
          for(int i=0;i<n;i++)
          {
               scanf("%lld",&a[i]);
               inserts(a[i]);
          }
          for(int i=0;i<n;i++)
          {
               ll mid=a[i]^searchs(a[i]);
               maxx=max(maxx,mid);
          }
          printf("%lld\n",maxx);
     }
     return 0;
}

呼呼

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值