字典树(Trie树)模板以及简单的入门题总结

字典树模板

//对于字符串比较多的要统计个数的,map被卡的情况下,直接用字典树
//很多题都是要用到节点下标来表示某个字符串
const int maxn =2e6+5;//如果是64MB可以开到2e6+5,尽量开大
int tree[maxn][30];//tree[i][j]表示节点i的第j个儿子的节点编号
bool flagg[maxn];//表示以该节点结尾是一个单词
int tot;//总节点数
void insert_(char *str)
{
   int  len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'0';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flagg[root]=true;
}
bool find_(char *str)//查询操作,按具体要求改动
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'0';
        if(!tree[root][id]) return false;
        root=tree[root][id];
    }
    return true;
}
void init()//最后清空,节省时间
{
    for(int i=0;i<=tot;i++)
    {
       flagg[i]=false;
       for(int j=0;j<10;j++)
           tree[i][j]=0;
    }
   tot=0;//RE有可能是这里的问题
}

字典树第一题

HDU1251
题意就是统计出以某个字符串为前缀的单词数量,首先构建出trie树并记录每个节点的访问次数,然后在上面查询就好了,模板题。
HDU1251代码

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
void insert_(char *str)
{
   int  len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       if(!tree[root][id]) tree[root][id]=++tot;
       sum[tree[root][id]]++;//记录节点访问次数
       root=tree[root][id];
   }
   //root在此对应某个单词,一一对应
}
int find_(char *str)
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        if(!tree[root][id]) return 0;
        root=tree[root][id];
    }
    return sum[root];//返回当前字符串结尾节点的访问次数,也就是作为前缀的出现次数
}
char ss[maxn];
int main()
{
    tot=0;
    while(gets(ss))
    {
        if(ss[0]=='\0') break;
        insert_(ss);
    }
    while(scanf("%s",ss)!=EOF)
    {
        printf("%d\n",find_(ss));
    }
    return 0;
}

字典树第二题

HDU2072
题意就是出现的不同单词个数
直接把字符全部插入Trie树中,然后统计所有具有flagg标记的节点个数就好了。
也可以边插入边统计,如果当前字符串结尾下标已经被标记,就不对答案做贡献,否则ans++.
第二题代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
bool flagg[maxn];
int tot;
void insert_(string str)
{
   int len=str.size();
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flagg[root]=true;
}
bool find_(string str)
{
    int len=str.size();
    int root=0;
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        if(!tree[root][id]) return true;//该单词没有出现过
        root=tree[root][id];
    }
    if(flagg[root]) return false;//出现过,不再算贡献
    else return true;
}
string str1,str2;
int main()
{
    ios::sync_with_stdio(false);
    while(getline(cin,str1))
    {
        if(str1=="#") break;
        int ans=0;
        stringstream ss(str1);
        while(ss>>str2)
        {
           if(find_(str2))
           {
               ans++;
               insert_(str2);
           }
        }
        cout<<ans<<endl;
        for(int i=0;i<tot;i++)
        {
            flagg[i]=false;
            for(int j=0;j<30;j++)
                tree[i][j]=0;
        }
        tot=0;
    }
    return 0;
}

字典树第三题

POJ2001
题意就是求一个能代表这个字符串的最短前缀,也就是只有这个字符串具有的前缀。
做法很显然,我们先构建好Trie树,然后对每个单词进行find,递归到直到节点出现次数为1,表示这个节点只有这一个单词走过,返回就ok。这里我用了string不断拼接字符,然后直接返回,减少了一些代码量。
POJ2001代码

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
void insert_(char *str)
{
   int  len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       if(!tree[root][id]) tree[root][id]=++tot;
       sum[tree[root][id]]++;
       root=tree[root][id];
   }
}
string find_(char *str)
{
    int len=strlen(str);
    int root=0;
    string ans="";
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        root=tree[root][id];
        ans+=str[i];
        if(sum[root]==1) return ans;
    }
}
char ss[1005][25];
int main()
{
    int tot=0;
    while(scanf("%s",ss[tot++])!=EOF)
    {
        insert_(ss[tot-1]);
    }
    for(int i=0;i<tot;i++)
    {
        printf("%s %s\n",ss[i],find_(ss[i]).c_str());
    }
    return 0;
}

字典树第四题

POJ3630
本题题意就是给你一个字符串集合,问你否所有的字符串都不是其他人的前缀。
首先我们构造出Trie树,然后对每个字符串find,当find的路径上如果出现其他字符串结尾标记,就说明其他字符串是当前字符串的前缀。注意这里对每个字符串find的时候只要搜索到 l e n − 1 len-1 len1即可,如果搜索到 l e n len len,那么将会将本身的字符串统计进去。
POJ3630代码

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][15];
bool flagg[maxn];
int tot;
void insert_(char *str)
{
   int  len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'0';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flagg[root]=true;
}
int find_(char *str)
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len-1;i++)
    {
        int id=str[i]-'0';
        root=tree[root][id];
        if(flagg[root]) return true;//路径上出现过其他字符串的结尾标记
    }
    return false;
}
char ss[10005][12];
int main()
{
    int n,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            scanf("%s",ss[i]);
            insert_(ss[i]);
        }
        int flag=0;
        for(int i=0;i<n;i++)
        {
            if(find_(ss[i]))
            {
                flag=1;
                break;
            }
        }
        if(flag==0)
            printf("YES\n");
        else
            printf("NO\n");
        for(int i=0;i<=tot;i++)
        {
            flagg[i]=false;
            for(int j=0;j<10;j++)
                tree[i][j]=0;
        }
        tot=0;
    }
    return 0;
}

字典树第五题

LightOJ1224
本题题意是让你找出一个字符串,使该字符串作为前缀的次数 ∗ * 该字符串的长度结果最大
我们首先构建好trie树,我们利用记录节点出现次数的方式存储,这时候结果就是 s u m [ r o o t ] ∗ 当 前 的 l e n sum[root]*当前的len sum[root]len,对于当前的len也就是递归深度,如果我们要存入所有节点之后再重新扫一遍Trie树复杂度会高很多,所以我们可以在插入字符串的时候进行统计,用一个maxx保存 s u m [ r o o t ] ∗ 当 前 l e n sum[root]*当前len sum[root]len的最大值就好了.具体实现看代码。
LightOJ1224代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<map>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][5];//只有四种字符
int sum[maxn];
int tot;
int ans;
map<char,int> mm;//字符映射
void insert_(char *str)
{
   int  len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=mm[str[i]];
       if(!tree[root][id]) tree[root][id]=++tot;
       sum[tree[root][id]]++;
       ans=max(ans,sum[tree[root][id]]*(i+1));//插入时直接统计结果
       root=tree[root][id];
   }
}
char ss[55];
int main()
{
    mm['A']=0;
    mm['C']=1;
    mm['G']=2;
    mm['T']=3;
    int t,n;
    scanf("%d",&t);
    int cnt=1;
    while(t--)
    {
        ans=0;
        scanf("%d",&n);
        while(n--)
        {
           scanf("%s",ss);
           insert_(ss);
        }
        for(int i=0;i<=tot;i++)
        {
            sum[i]=0;
            for(int j=0;j<4;j++)
                tree[i][j]=0;
        }
        tot=0;
        printf("Case %d: %d\n",cnt++,ans);
    }
    return 0;
}

字典树第六题

POJ2513
本题题意是给一堆木棒,每种木棒左右两端有两种颜色,木棒进行拼接的时候,只有相同颜色之间才可以拼接,问最后是否可以将所有木棒拼为一根木棒。
我们考虑把同一种颜色的点聚在一起,我们就可以得到一个无向图,如果这个无向图是欧拉图,代表展开之后可以一笔走完,也就是可以连接成一条木棒。所以我们用trie树判断每种颜色出现的次数,再用并查集判一下图是否连通,最后用欧拉图的性质判断一下是否为欧拉图就好了(只存在两个或者0个奇度的点)
POJ2513代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int rank_[maxn];
int f[maxn];
int vis[maxn];
int flag,flag2;
int tot;
int cnt;
int pos1,pos2;
int insert_(char *str)
{
   int len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   if(!vis[root]) vis[root]=++cnt;
   if(f[cnt]==0) f[cnt]=cnt;//在这里重置f数组,节省一部分时间
   return vis[root];
}
int find_(int x){ return f[x]==x?x:f[x]=find_(f[x]);}
void union_(int x,int y)
{
    x=find_(x),y=find_(y);
    if(x!=y)
    {
        if(rank_[x]>rank_[y])
        {
            f[y]=x;
        }
        else
        {
            f[x]=y;
            if(rank_[x]==rank_[y]) rank_[x]++;
        }
    }
}
char str1[12],str2[12];
int main()
{
    while(scanf("%s%s",str1,str2)!=EOF)
    {
       pos1=insert_(str1);//统计该颜色对应的下标
       sum[pos1]++;
       pos2=insert_(str2);
       sum[pos2]++;//统计每种颜色的度
       union_(pos1,pos2);
    }
    for(int i=1;i<=cnt;i++)
    {
       if(sum[i]%2==1)
       {
           flag++;
       }
       if(find_(i)!=find_(1))
       {
           printf("Impossible\n");//非连通图
           return 0;
       }
    }
    if(flag==0||flag==2) printf("Possible\n");//欧拉图判定
    else printf("Impossible\n");
    return 0;
}

字典树第七题

POJ1451
本题题意比较有意思,大概就是模拟手机输入法,先给你一个用户的词库,即每个单词出现的次数,这个时候再按照九键输入法给你一个数字序列,问你在输入这个序列的过程中,出现的字符串顺序,也就是对于每个数字序列,给出一个最有可能出现的字符串。
这道题我的做法比较巧妙,但是不怎么会算复杂度,还是很快的过去了。首先我们考虑,对于每个数字序列,我们都可以用一个string去映射,这样我们可以用一个map来离线保存每个数字序列对应的最有可能出现的字符串,可是我们怎么知道每个数字序列最有可能出现的字符串是哪个呢,首先我们对单词进行映射,把每个单词映射成数字序列,然后在插入的过程中,对于每一个前缀都更新一下这个前缀对应的map,最后查询的时候就可以直接输出了。
POJ1451代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
#include<map>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
int belong[30];
map<string,int> mm;//每种数字序列最大出现次数
map<string,string>mm2;//每种序列对应的最大次数的字符串
void insert_(char *str,int num)
{
   int len=strlen(str);
   int root=0;
   string strr="";
   string str2="";
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       strr+=(belong[id]+'0');//将插入的字符串映射成数字序列
       str2+=str[i];//当前前缀
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
       sum[root]+=num;//统计每个前缀出现次数
       if(mm[strr]<sum[root])//更新map
       {
           mm[strr]=sum[root];
           mm2[strr]=str2;
       }
   }
   return ;
}
char ss[maxn];
void init()//数字与字母间的映射
{
    for(int i=0;i<25;i++)
    {
        if(i<18)
            belong[i]=2+i/3;
        else
            belong[i]=2+(i-1)/3;
    }
    belong[25]=9;
    return ;
}
int main()
{
    init();
    int cnt=1;
    int n,m,t,num;
    scanf("%d",&t);
    while(t--)
    {
        mm.clear();
        mm2.clear();
        scanf("%d",&n);
        while(n--)
        {
            scanf("%s%d",ss,&num);
            insert_(ss,num);
        }
        scanf("%d",&m);
        printf("Scenario #%d:\n",cnt++);
        while(m--)
        {
            scanf("%s",ss);
            string tmp="";
            int len=strlen(ss);
            for(int i=0;i<len-1;i++)
            {
                tmp+=ss[i];//对于每个前缀直接输出
                if(!mm2.count(tmp)) printf("MANUALLY\n");
                else printf("%s\n",mm2[tmp].c_str());
            }
            printf("\n");
        }
        for(int i=0;i<tot;i++)
        {
           sum[i]=0;
            for(int j=0;j<30;j++)
                tree[i][j]=0;
        }
        printf("\n");
    }
    return 0;
}

字典树第八题

POJ1816
本题的题意是给你n个模式串和m个匹配串,模式串中有 ? 和 ∗ ?和* 两种字符,?可以匹配任意一种字符, ∗ * 可以匹配任意个字符,问每种匹配串可以和之前哪些模式串匹配。
我们先构建好字典树,对于每个匹配串实现find,find的时候用类似dfs的写法,如果当前节点的字符或者?存在,直接dfs下一个位置的字符,如果当前字符对应的位置有 ∗ * 存在,那么就从匹配串之后的每一个位置进行往下dfs,因为可以匹配任意个字符。而dfs计数的条件是匹配串匹配结束而且达到模式串某个结尾标记。则在这个模式串上做标记。需要注意的细节是此时不能直接return,因为有可能此时的字符串和剩下的匹配串还能匹配,类似模式串A : AA** 模式串B: AA*** 匹配串为AAA ,那么匹配到A字符串之后则不能停止,要继续往下搜索。
POJ1816代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
bool flag[maxn];
bool vis[maxn];
int tot;
int insert_(char *str)
{
   int len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id;
        if(str[i]=='*') id=26;//*号存在第26位
        else if(str[i]=='?') id=27;//?存在第27位
        else   id=str[i]-'a';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flag[root]=true;//结尾符标记
   return root;
}
void find_(char *str,int pos1,int pos2)//pos1 模式串下标 pos2 匹配串下标
{
    int len=strlen(str);
    if(pos2==len&&flag[pos1])//达到条件则对该字符串进行标记
    {
        vis[pos1]=1;//对该单词的下标进行标记
    }
    int root=pos1;
    int id=str[pos2]-'a';
    if(tree[root][id])//存在相同字符
    {
        int tmp=tree[root][id];
        find_(str,tmp,pos2+1);
    }
    if(tree[root][27])//存在?
    {
        int tmp=tree[root][27];
        find_(str,tmp,pos2+1);
    }
    if(tree[root][26])//存在*
    {
        int tmp=tree[root][26];
        for(int j=pos2;j<=len;j++)//对之后的每一个位置进行dfs,要注意从pos2开始,因为*号可以不匹配
            find_(str,tmp,j);
    }
    return ;
}
char ss[maxn];
int pos[maxn];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%s",ss);
        pos[i]=insert_(ss);//插入时顺带获取下标
    }
    while(m--)
    {
        int flag=0;
        scanf("%s",ss);
        find_(ss,0,0);
        for(int i=0;i<n;i++)
        {
            if(vis[pos[i]])
            {
                flag=1;
                 printf("%d ",i);
            }
        }
        for(int i=0;i<n;i++)
        {
            if(vis[pos[i]])
            {
                vis[pos[i]]=0;
            }
        }
        if(flag==0) printf("Not match");
        printf("\n");
    }
    return 0;
}

字典树第九题

HDU1247
本题题意是问你某个单词是否可以拆成单词表中的其他两个单词。
我们可以建两颗Trie树,然后分别正序倒序插入每个单词,对每个单词查询的时候,我们分别正序倒序查询,对出现过单词的前缀下标进行标记,对每个出现过单词的后缀进行标记,最后扫描标记数组,如果某个位置前缀后缀均被标记过,则表示可以拆成单词表中的两个其他单词。
HDU1247代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
#include<set>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30],tree2[maxn][30];
set<string> s;
bool flagg[maxn],flagg2[maxn];
int sum[55];
int tot,tot2;
void insert_(char *str)
{
   int len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flagg[root]=true;
   return ;
}
void find_(char *str)
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len-1;i++)
    {
        int id=str[i]-'a';
        root=tree[root][id];
        if(flagg[root]) sum[i]++;//当前前缀是其他单词
    }
    return ;
}
void insert2_(char *str)
{
   int len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'a';
       if(!tree2[root][id]) tree2[root][id]=++tot2;
       root=tree2[root][id];
   }
   flagg2[root]=true;
   return ;
}
void  find2_(char *str)
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len-1;i++)
    {
        int id=str[i]-'a';
        root=tree2[root][id];
        if(flagg2[root])
        {
            sum[len-i-2]++;//当前前缀是其他单词,对反转后的位置进行标记
        }
    }
    return ;
}
char ss[50005][55];
int main()
{
    int cnt=0;
    while(scanf("%s",ss[cnt++])!=EOF)
    {
        insert_(ss[cnt-1]);//正序插入到第一棵树
        strrev(ss[cnt-1]);
        insert2_(ss[cnt-1]);//倒叙插入第二颗树
        strrev(ss[cnt-1]);
    }
    for(int i=0;i<cnt;i++)
    {
        int len=strlen(ss[i]);
        for(int j=0;j<len;j++)
        {
            sum[j]=0;
        }
        find_(ss[i]);//在第一棵树上查询并标记
        strrev(ss[i]);
        find2_(ss[i]);//在第二颗树上查询并标记
        strrev(ss[i]);
        for(int j=0;j<len;j++)
        {
            if(sum[j]==2)//两次查询均标记过的位置
            {
                string tmp=ss[i];
                s.insert(tmp);
                break;
            }
        }
    }
    set<string>::iterator it;
    for(it=s.begin();it!=s.end();++it)
    {
        printf("%s\n",(*it).c_str());
    }
    return 0;
}

字典树第十题

POJ2408
本题题意是,把可以通过重新排列变成相同单词的单词放入一个集合,最后按照集合元素由多到少输出前五个集合,如果集合元素相同,按照字典序由小到大输出
我们考虑,如果两个单词可以通过重新排列组合变成相同单词,那么他们的字典序最小的排列方式一定是相同的,所以我们可以利用每个元素的最小排列方式判定是否在同一个集合,字典树在这里用于判定某个字符串时候出现过。最后用set来保存以便维持字典序
POJ2408代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<set>
#include<vector>
using namespace std;
const int maxn = 1e6+5;
int tree[maxn][30];
char ss[maxn];
int tot,anstot;
int num[30];
int vis[maxn];
struct data
{
    int num;
    set<string> s;
}cnt[maxn];
bool cmp(const data &a,const data &b)
{
    if(a.num==b.num)
    {
        return *(a.s.begin())<*(b.s.begin());
    }
    return a.num>b.num;
}
void insert_(char *str)
{
    int root=0;
    int len= strlen(str);
    string str2="";
    for(int i=0;i<len;i++)
    {
        num[str[i]-'a']++;
    }
    for(int i=0;i<26;i++)//在此转换为字典序最小的字符串
    {
        while(num[i]!=0)
        {
            str2+=(i+'a');
            num[i]--;
        }
    }
    for(int i=0;i<len;i++)
    {
        int id=str2[i]-'a';
        if(!tree[root][id]) tree[root][id]=++tot;
        root=tree[root][id];
    }
    if(!vis[root])  vis[root]=++anstot;//若未出现过,赋予编号
    cnt[vis[root]].num++;//将此字符串所在的结构体num变量+1
    string tmp=str;
    cnt[vis[root]].s.insert(tmp);
    return ;
}
int main()
{
    while(scanf("%s",ss)!=EOF)
    {
        insert_(ss);
    }
    sort(cnt+1,cnt+1+anstot,cmp);
    for(int i=1;i<=5;i++)
    {
        printf("Group of size %d: ",cnt[i].num);
        set<string>::iterator it;
        for(it=cnt[i].s.begin();it!=cnt[i].s.end();++it)
        {
            printf("%s ",(*it).c_str());
        }
        printf(".\n");
    }
    return 0;
}

字典树第十一题

HDU1075
本题的题意是给你火星文与地球文的映射方式,然后给你一个火星文组成的文本,若某单词在映射文本中出现过,则输出映射之后的文本。否则输出原文本。
我们可以建立trie树,插入火星文本,将返回的下标pos与地球文相对应,在翻译文本的时候,从前往后截取每一段单词,在trie树上查找该单词是否出过,要注意必须是单词,而不能是某单词的前缀。若找到则返回下标,然后输出该下标对应的地球文,否则返回-1,输出原文本。
HDU1075代码

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
using namespace std;
const int maxn = 2e6+5;
int tree[maxn][30];
int flagg[maxn];
int pos[maxn];
int tot,tot2;
int insert_(char *str)
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        if(!tree[root][id]) tree[root][id]=++tot;
        root=tree[root][id];
    }
    flagg[root]=true;
    return root;//返回下标,对应的是一个单词
}
int find_(char *str)
{
    int root=0;
    int len=strlen(str);
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        if(!tree[root][id]) return -1;
        root=tree[root][id];
    }
    if(flagg[root]) return root;//若为完整相匹配单词,返回下标
    else return -1;
}
char ss1[maxn][12];
char tmp[maxn];
int main()
{
    int cnt=0;
    scanf("%s",tmp);
    while(scanf("%s",ss1[cnt])!=EOF)
    {
        if(ss1[cnt][0]=='E')
        {
            scanf("%s",tmp);
            break;
        }
        scanf("%s",tmp);
        pos[insert_(tmp)]=cnt;//将返回的下标与地球文进行匹配
        cnt++;
    }
    getchar();
    while(gets(tmp)!=NULL)
    {
        if(tmp[0]=='E') break;
        int len=strlen(tmp);
        char str[12];
        int cnt;
        for(int i=0;i<len;i++)
        {
            if(tmp[i]>='a'&&tmp[i]<='z'&&i<len)
            {
                cnt=0;
                while(tmp[i]>='a'&&tmp[i]<='z')
                {
                    str[cnt++]=tmp[i++];
                }
                str[cnt]='\0';//截取单词,存在str中
                int pp=find_(str);
                if(pp!=-1)
                    printf("%s",ss1[pos[find_(str)]]);//输出对应的地球文
                else
                    printf("%s",str);
                printf("%c",tmp[i]);
            }
            else
                printf("%c",tmp[i]);
        }
        printf("\n");
    }
    return 0;
}

字典树第十二题

Lightoj1269
本题的题意是求出一段连续的区间,使这段区间异或和最小/最大。分别输出最大异或值和最小异或值。
区间异或和的题目一般都要用到前缀异或和,我们这里可以预处理出前缀异或和,然后我们考虑,对于每个值,在字典树上查找之前出现过的最优的匹配,利用字典树的特点,将其拆为二进制,贪心的去查找,查找最大值则是0找1,1找0,查找最小值则是1找1,0找0。维护一下最大值与最小值即可。Find的时候可以利用两个不同的root并行查找最大值与最小值,减少代码量。
Lightoj1269代码

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =3e6+5;
int tree[maxn][2];
int tot;
int ans,ans2;
int flagg[maxn];
void insert_(int num)
{
    int root=0;
    int pos=30;
    int id;
    while(pos!=-1)
    {
        if(num&(1<<pos)) id=1;
        else id=0;
        if(!tree[root][id]) tree[root][id]=++tot;
        root=tree[root][id];
        pos--;
    }
    return ;
}
void find_(int num)
{
    int root=0;
    int root2=0;
    int pos=30;
    int id;
    ans=0;
    ans2=0;
    while(pos!=-1)
    {
        if(num&(1<<pos))  id=1;
        else id=0;
        if(tree[root][id^1])//最大值优先找相反的0-1,1-0
        {
            ans^=(1<<pos);
            root=tree[root][id^1];
        }
        else
        {
            root=tree[root][id];
        }
        if(tree[root2][id])//最小值优先找相同的0-0,1-1
        {
            root2=tree[root2][id];
        }
        else
        {
            ans2^=(1<<pos);
            root2=tree[root2][id^1];
        }
        pos--;
    }
}
int a[maxn];
int sum[maxn];
int main()
{
    int n,t;
    int cnt=1;
    scanf("%d",&t);
    while(t--)
    {
        tot=0;
        int maxx=0;
        int minn=0x3f3f3f3f;
        scanf("%d",&n);
        insert_(0);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sum[i]=a[i]^sum[i-1];
            find_(sum[i]);//先查找
             minn=min(ans2,minn);
             maxx=max(ans,maxx);
            insert_(sum[i]);//后插入,避免本身造成影响
        }
         printf("Case %d: %d %d\n",cnt++,maxx,minn);
         for(int i=0;i<=tot;i++)
         {
             tree[i][0]=0;
             tree[i][1]=0;
         }
    }
    return 0;
}

字典树第十三题

UVA-10887
本题题意就是给你AB两个字符串集,问你以A中字符串作为前缀,B中字符串作为后缀,可以得到多少个不同的字符串,就对每个拼接后的字符串放进字典树,统计单词个数就可以了

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 2e6+5;
int root,cnt;
int ans;
int tree[maxn][26];
int end_[maxn];
int newnode()
{
    end_[cnt]=0;
    for(int i=0;i<26;i++)
        tree[cnt][i]=-1;
    return cnt++;
}
void init()
{
    ans=0;
    cnt=0;
    root=newnode();
}
void insert_(string str)
{
    int tmp=root;
    int len=str.size();
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        if(tree[tmp][id]==-1)
        {
            tree[tmp][id]=newnode();
        }
        tmp=tree[tmp][id];
    }
    if(end_[tmp]==0)
    {
        end_[tmp]=1;
        ans++;
    }
    return ;
}
string str1[1505];
string str2[1505];
string str3;
char tmp1[15];
char tmp2[15];
int main()
{
    int n,m,t;
    int cnt=1;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        getchar();
        for(int i=0;i<n;i++)
        {
            gets(tmp1);//读入可能有空行,用gets
            str1[i]=tmp1;
        }
        for(int i=0;i<m;i++)
        {
            gets(tmp2);
            str2[i]=tmp2;
        }
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                str3=str1[i]+str2[j];//拼接
                insert_(str3);
            }
        }
        printf("Case %d: %d\n",cnt++,ans);
    }
    return 0;
}

未完待续…

    原文作者:Trie树
    原文地址: https://blog.csdn.net/qq_38891827/article/details/80532462
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞