字典树

Posted iwillenter-top1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字典树相关的知识,希望对你有一定的参考价值。

(并不是按照难度来排序的)

(1.doubt)

题意:

(VKorpela)很喜欢异或,有一天,他看到(Serene)写下了两个长度都为(n)的数组(a)(b),他想对(a)(b)分别按照某种方式排序,然后构造一个数组(c),满足(??_?? = ??_?? xor ??_??) 。他想请你告诉他字典序最小的(c)

分析:

一看就知道是字典树对不对,但是不会打(Σ( ° △ °|||)︴)

题解思路如下:对于(a)(b)分别建一棵字典树,两棵树一起(dfs)使得(a_i)(b_j)抵消。

由异或的性质:如果两者相同,就可以相消,所以先合并(lson[A],lson[B]),再合并(rson[A])(rson[B])(这两步的顺序可以调换),再合并(lson[A],rson[B],)最后合并(rson[A],lson[B])(同样的,这两步的顺序也可以调换,为什么可以调换:因为(lson)(rson)之间互不影响).如果看不懂,就请看代码:

(Code:)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxm=12e6+10;
const int maxn=2e5+10;
int son[maxm][2],sum[maxm],tot;
int a[maxn],b[maxn],c[maxn];
int cnt;

void insert(int x,int rt)//因为是建两棵树
{
    ++sum[rt];//这不应该是没有必要的,去掉之后依然A掉
    for(int i=(1<<30);i;i>>=1)//2^0=1
    {
        bool v=x&i;
        if(!son[rt][v]) son[rt][v]=++tot;
        rt=son[rt][v];
        ++sum[rt];
    }
}

#define l1 sum[son[p1][0]]
#define r1 sum[son[p1][1]]
#define l2 sum[son[p2][0]]
#define r2 sum[son[p2][1]]

int dfs(int p1,int p2,int x,int d)
{
    int u=0;
    if(d<0)//加上两棵树的总共有32层,所以最底层的时候d=-1
    {
        u=min(sum[p1],sum[p2]);
        sum[p1]-=u,sum[p2]-=u;
        for(int i=1;i<=u;++i) c[++cnt]=x;
        return u;
    }
    if(l1&&l2) u+=dfs(son[p1][0],son[p2][0],x,d-1);//d-1
    if(r1&&r2) u+=dfs(son[p1][1],son[p2][1],x,d-1);
    if(l1&&r2) u+=dfs(son[p1][0],son[p2][1],x+(1<<d),d-1);//因为在这一位的时候两者不同异或后的结果要加上(1<<d)(表示的是它儿子这一位要加上1<<d)
    if(r1&&l2) u+=dfs(son[p1][1],son[p2][0],x+(1<<d),d-1);//原因同上
    sum[p1]-=u,sum[p2]-=u;
    return u;
}
void clear()
{
    for(int i=1;i<=tot;++i) son[i][0]=son[i][1]=0,sum[i]=0;
    tot=2,cnt=0;//因为两棵树的根节点分别记为了1和2
}
int main()
{
    freopen("doubt.in","r",stdin);
    freopen("doubt.out","w",stdout);
    int n;
    scanf("%d",&n);
    clear();
    for(int i=1;i<=n;++i) scanf("%d",&a[i]),insert(a[i]);
    for(int i=1;i<=n;++i) scanf("%d",&b[i]),insert(b[i]);
    dfs(1,2,0,30);
    sort(c+1,c+n+1);//要使字典序最小,一定要先排序
    for(int i=1;i<n;++i) printf("%d ",c[i]);
    printf("%d
",c[n]);
    return 0;
}

(2.Luogu2922) (Secret Message)

题意:

贝茜正在领导奶牛们逃跑.为了联络,奶牛们互相发送秘密信息.

信息是二进制的,共有(M(1≤M≤50000))条.反间谍能力很强的约翰已经部分拦截了这些信息,知道了第(i)条二进制信息的前(b_i(l<b_i≤10000))位.他同时知道,奶牛使用(N(1≤N≤50000))条密码.但是,他仅仅了解第(j)条密码的前(c_j(1≤c_j≤10000))位.

对于每条密码(j),他想知道有多少截得的信息能够和它匹配.也就是说,有多少信息和这条密码有着相同的前缀.当然,这个前缀长度必须等于密码和那条信息长度的较小者.

在输入文件中,位的总数(即(∑B_i+∑C_i))不会超过(500000).

分析:

很简单的模板题

对于密码,若它与信息有相同的前缀(题目中的前缀有定义,并非我们平时所说的前缀),有两种情况:

(1.)密码包含信息

(2.)信息包含密码

第一种情况边走边统计即可,第二种情况,在最末的时候加上经过这个节点的字符串个数,同时又要减去在这个节点结束的字符串个数,因为在之前已经加上了.

(Code:)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int maxn=500010;
const int maxm=5e7+10;
int son[maxm][2],sum[maxm];
int a[maxn];
int add[maxn];
int tot=0;
void insert(int *s,int num)
{
    int rt=0;
    for(int i=1;i<=num;++i)
    {
        int c=s[i];
        if(!son[rt][c]) son[rt][c]=++tot;
        rt=son[rt][c];
        ++add[rt];
    }
    ++sum[rt];
}
int query(int* s,int num)
{
    int rt=0;
    int res=0;
    for(int i=1;i<=num;++i)
    {
        int c=s[i];
        if(!son[rt][c]) return res;
        else
        {
            rt=son[rt][c];
            res+=sum[rt];
        }
    }
    return res+add[rt]-sum[rt];
}
int main()
{
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;++i)
    {
        int k;
        scanf("%d",&k);
        for(int j=1;j<=k;++j)
        {
            scanf("%d",&a[j]);
        }
        insert(a,k);
    }
    for(int i=1;i<=n;++i)
    {
        int k;
        scanf("%d",&k);
        for(int j=1;j<=k;++j)
        {
            scanf("%d",&a[j]);
        }
        printf("%d
",query(a,k));
    }
    return 0;
}

(3.)单词数

注:因为我不用(hdu),所以下面的代码也没有经过评测

题意:

统计一篇文章中不同的单词数

分析:

也是模板题(其实如果只考字典树的话,都只是在它的模板上稍微修改一下即可,只有第一题可能比较难),收获也就是(stringstream)的应用

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int maxn=2e6+10;
int ch[maxn][30];
int tot=0;
int ans=0;
bool flag[maxn];
void insert(char* a)
{
    int rt=0;
    int l=strlen(a);
    for(int i=0;i<l;++i)
    {
        int c=a[i]-'a';
        if(!ch[rt][c]) ch[rt][c]=++tot;
        rt=ch[rt][c];
    }
    if(!flag[rt]) ++ans;
    flag[rt]=true;
}
void clear()
{
    for(int i=0;i<=tot;++i)
    {
        flag[i]=false;
        for(int j=0;j<=26;++j)
        {
            ch[i][j]=0;
        }
    }
    tot=ans=0;
}
int main()
{
    std::ios::sync_with_stdio(0)
    string s,tp;
    while(getline(cin,s))
    {
        if(s==="#") break;
        stringstream ss(s);
        while(ss>>tp)
        {
            insert(tp);
        }
        printf("%d
",ans);
        clear();
    }
    return 0;
}

有一个更简易的(set)做法,相信大家都会,我就不写啦(QAQ)

(4.POJ) (1816) (Wild Words)

题意:

给出(n)个模式串,串中除开小写字母外,(?)代表一个字符,(*)代表可空的任意字符串,然后再给出(m)个字符串,问有多少个模式串可以与之匹配。

分析:

看有一个题解写的是这个东西叫做字符串模糊匹配,其实好像就是一个(tire+dfs)

总的来说,除了('?')('#')的处理外,这就是一个字典树的模板题

对于('?')的处理,就把它的编号设为(26),任意一个字符经过它都可以继续往下走。

对于('#')的处理,每次让它匹配(1)个字符,(2)个字符(,3)个字符(,...,)只要使它的长度始终小于(len)即可,详情请见代码:

(Code:)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int maxn=6e5+10;

int ch[maxn][30];
bool flag[maxn];
int tot=0;
int en[100000];
bool ans[maxn];
int n,m;
int ID(char s)
{
    if(s=='?') return 26;
    if(s=='*') return 27;
    return s-'a';
}
int insert(char* s)
{
    int rt=0;
    int len=strlen(s);
    for(int i=0;i<len;++i)
    {
        int c=ID(s[i]);
        if(!ch[rt][c]) ch[rt][c]=++tot;
        rt=ch[rt][c];
    }
    flag[rt]=true;
    return rt;
}
void check(char* s,int pos,int c)
{
    if(pos==strlen(s)&&flag[c])
    {
        ans[c]=true;
    }
    int num=s[pos]-'a';
    if(num>=0&&num<=25&&ch[c][num]) check(s,pos+1,ch[c][num]);
    if(ch[c][26]) check(s,pos+1,ch[c][26]);
    if(ch[c][27])
    {
        int exc=pos;
        while(exc<=strlen(s))
        {
            check(s,exc,ch[c][27]);
            ++exc;
        }
    }
}
int main()
{
    char s[30];
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i)
    {
        cin>>s;
        en[i]=insert(s);
    }
    for(int i=0;i<m;++i)
    {
        cin>>s;
        memset(ans,false,sizeof(ans));
        int match=0;
        check(s,0,0);
        for(int j=0;j<n;++j)
        {
            if(ans[en[j]]) printf("%d ",j),match++;
        }
        if(!match) printf("Not match");
        putchar('
');
    }
    return 0;
}

以上是关于字典树的主要内容,如果未能解决你的问题,请参考以下文章

字典树(java)

字典树(java)

Python代码阅读(第19篇):合并多个字典

统计难题HDU - 1251map打表或字典树字典树模板

Python代码阅读(第26篇):将列表映射成字典

字典树——入门学习(java代码实现)