字典树

字典树:又称单词查找树,trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高

 

 

https://www.acwing.com/problem/content/144/

题意:

给定N个字符串S1,S2…SNS1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1S1~SNSN中有多少个字符串是T的前缀

解析:

用一个数组对每个单词的末尾累计

ac:

#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
int trie[MAXN][27];
int num[MAXN];
int pos;

void ins(char c[])
{
    int rt=0;
    int len=strlen(c);
    for(int i=0;i<len;i++)
    {
        int id=c[i]-'a';
        if(trie[rt][id]==0)
            trie[rt][id]=++pos;
        rt=trie[rt][id];
    }
    num[rt]++;
}

int getsum(char c[])
{
    int rt=0;
    int ans=0;
    int len=strlen(c);
    for(int i=0;i<len;i++)
    {
        int id=c[i]-'a';
        if(trie[rt][id]==0)
            return ans;
        rt=trie[rt][id];
        ans+=num[rt];
    }
    return ans;
}

char s[MAXN];

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

The XOR Largest Pair

https://loj.ac/problem/10050

在给定的N个整数A1,A2……AN中选出两个进行xor运算,得到的结果最大是多少?

解析:

将数字转化为二进制数,从左往右优先选择同一位,不同的两个数

用字典树来实现

ac:

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int trie[MAXN*30][3];
int pos=0;

void ins(int x)
{
    int rt=0;
    for(int i=31;i>=0;i--)
    {
        int y=(x>>i)&1;
        if(trie[rt][y]==0)
            trie[rt][y]=++pos;
        rt=trie[rt][y];
    }
}

int get(int x)
{
    int rt=0,ans=0;
    for(int i=31;i>=0;i--)
    {
        int y=(x>>i)&1;
        int c=y^1;
        if(trie[rt][c])
            rt=trie[rt][c],ans=ans<<1|1;
        else
            rt=trie[rt][y],ans=ans<<1;
    }
    return ans;
}

int main()
{
    int n,x,ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        ans=max(ans,get(x));
        ins(x);
    }
    printf("%d\n",ans);
    return 0;
}

链接:https://vjudge.net/problem/HDU-6625

题意:

给定两个数组a[i],b[i],可以任意改变数组的顺序,使得数组的字典序最小,其中c[i]=a[i]^b[i].

解析:

建立两颗01字典树,同时在两颗字典树上搜索,尽可能的选择相同的路径走,优先走00,11,其次01,10.以这样方法贪心得到c[i]数组,然后对c[i]数组排序

ac:

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;

struct node
{
    int trie[MAXN*30][3];
    int num[MAXN*30];
    int pos=0;
    void ins(int x)
    {
        int rt=0;
        for(int i=30;i>=0;i--)
        {
            int y=(x>>i)&1;
            if(trie[rt][y]==0)
                trie[rt][y]=++pos;
            rt=trie[rt][y];
            num[rt]++;
        }
    }
}A,B;
int a[MAXN],b[MAXN];
int c[MAXN];

void init()
{
    for(int i=0;i<=A.pos;i++)
        A.trie[i][1]=A.trie[i][0]=A.num[i]=0;
    for(int i=0;i<=B.pos;i++)
        B.trie[i][1]=B.trie[i][0]=B.num[i]=0;
    A.pos=B.pos=0;
}

void solve(int n)
{
    for(int i=1;i<=n;i++)
    {
        c[i]=0;
        int nowA=0,nowB=0;
        for(int j=30;j>=0;j--)
        {
            if(A.num[A.trie[nowA][0]]&&B.num[B.trie[nowB][0]])
            {
                nowA=A.trie[nowA][0],nowB=B.trie[nowB][0];
                A.num[nowA]--;B.num[nowB]--;
            }
            else if(A.num[A.trie[nowA][1]]&&B.num[B.trie[nowB][1]])
            {
                nowA=A.trie[nowA][1],nowB=B.trie[nowB][1];
                A.num[nowA]--;B.num[nowB]--;
            }
            else if(A.num[A.trie[nowA][1]]&&B.num[B.trie[nowB][0]])
            {
                nowA=A.trie[nowA][1],nowB=B.trie[nowB][0];
                A.num[nowA]--;B.num[nowB]--;
                c[i]+=(1<<j);
            }
            else if(A.num[A.trie[nowA][0]]&&B.num[B.trie[nowB][1]])
            {
                nowA=A.trie[nowA][0],nowB=B.trie[nowB][1];
                A.num[nowA]--;B.num[nowB]--;
                c[i]+=(1<<j);
            }
        }
        cout<<c[i]<<endl;
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        init();
        int n,x,ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),A.ins(a[i]);
        for(int i=1;i<=n;i++)
            scanf("%d",&b[i]),B.ins(b[i]);
        solve(n);
        sort(c+1,c+n+1);
        for(int i=1;i<n;i++)
            printf("%d ",c[i]);
        printf("%d\n",c[n]);
    }
    return 0;
}
/*
1
2
11 10
10 9
*/

CF842D

题意:

给定N个数字,然后对于M个询问的K,求N个数字与K异或后得到的新数列的mex,就是在数字集合中未出现过的最小的非负数

解析:

很多二进制关于异或求最值的问题,都可以转换为字典树来求

异或有一些性质:

结合律:a^(x^c)=(a^x)^c,所以我们不需要每个数都去异或

如果x!=y,x^k!=y^k                             如果x^k!=y^k,x!=y

求mex(a[i]^k)等价与mex(不等于任何a[i]^k),即将不出现的值和k异或出最小的情况,注意30w要*2,30w并不是能异或出的最大情况

我们直接将(0~60w)的不出现在a[]的数加入01字典树中,然后求出k与这个字典树中异或出的最小值情况

ac:

#include<bits/stdc++.h>
#define MAXN 600005
using namespace std;
int trie[MAXN*25][3];
int pos=0;
int vis[MAXN];

void ins(int x)
{
    int rt=0;
    for(int i=19;i>=0;i--)
    {
        int c=(x>>i)&1;
        if(trie[rt][c]==0)
            trie[rt][c]=++pos;
        rt=trie[rt][c];
    }
}

int get(int x)
{
    int ans=0;
    int rt=0,d=-1;
    for(int i=19;i>=0;i--)
    {
        int c=(x>>i)&1;
        if(trie[rt][c])//尽量走相同的
            rt=trie[rt][c];
        else if(trie[rt][c^1])//走不同的
        {
            rt=trie[rt][c^1];
            ans+=(1<<i);
        }
        else{//走不了,就跳出,然后后面每种情况都算上
            d=i;
            break;
        }
    }
    for(int i=d;i>=0;i--)
    {
        int c=(x>>i)&1;
        ans+=(1<<i);
    }
    return ans;
}

int main()
{
    int n,m,x;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        vis[x]++;
    }
    for(int i=0;i<=600000;i++)
        if(vis[i]==0)
            ins(i);
    int y=0;
    while(m--)
    {
        scanf("%d",&x);
        y=y^x;                //异或运算有结合律
        printf("%d\n",get(y));
    }
    return 0;
}

https://ac.nowcoder.com/acm/contest/1221/B

题意:

给定n个字符串,然后询问与x字符串字串想等的个数*len的和,不能算自己的子串

解析:

字典树直接处理,ins有3个功能,插入,删除,查询,因为不能算自己的子串,所以要先删除,再查询,然后再插回去

因为字串种类非常多,所以trie[x][26]的x会非常大,x要开大一点

ac:

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int trie[MAXN*26][26];
int cnt[MAXN*26];
int tot=0;

int ins(char c[],int st,int len,int sign)
{
    int now=0;
    for(int i=st,j=0;j<len;j++,i++)
    {
        int id=c[i]-'a';
        if(trie[now][id]==0)
            trie[now][id]=++tot;
        now=trie[now][id];
    }
    if(sign==1)     //插入
        cnt[now]++;
    else if(sign==2)//删除
        cnt[now]--;
    else            //查询
        return cnt[now];
}

char s[50005][10];

int main()
{
    int n,kk,q,x,len;
    scanf("%d%d",&n,&kk);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s[i]);
        for(int j=0;j<kk;j++){
            for(int k=1;k+j<=kk;k++){
                ins(s[i],j,k,1);
            }
        }
    }
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d%d",&x,&len);
        for(int i=0;i+len<=kk;i++)
            ins(s[x],i,len,2);
        int ans=0;
        for(int i=0;i+len<=kk;i++)
            ans+=ins(s[x],i,len,3)*len;
        for(int i=0;i+len<=kk;i++)
            ins(s[x],i,len,1);
        printf("%d\n",ans);
    }
    return 0;
}
/*
2 3
abc
abc
2
1 3
1 2
*/

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值