码迷,mamicode.com
首页 > 其他好文 > 详细

字典树

时间:2019-11-09 23:45:31      阅读:104      评论:0      收藏:0      [点我收藏+]

标签:情况   with   int   一个   之间   数组   部分   为什么   cin   

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

\(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\n",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\n",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\n",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('\n');
    }
    return 0;
}

字典树

标签:情况   with   int   一个   之间   数组   部分   为什么   cin   

原文地址:https://www.cnblogs.com/iwillenter-top1/p/11828174.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!