HDU 6096-String

来源:互联网 发布:安卓应用市场排名优化 编辑:程序博客网 时间:2024/05/22 06:34

String

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1308    Accepted Submission(s): 431


题目链接:点击打开链接



Problem Description
Bob has a dictionary with N words in it.
Now there is a list of words in which the middle part of the word has continuous letters disappeared. The middle part does not include the first and last character.
We only know the prefix and suffix of each word, and the number of characters missing is uncertain, it could be 0. But the prefix and suffix of each word can not overlap.
For each word in the list, Bob wants to determine which word is in the dictionary by prefix and suffix.
There are probably many answers. You just have to figure out how many words may be the answer.
 

Input
The first line of the input gives the number of test cases T; T test cases follow.
Each test case contains two integer N and Q, The number of words in the dictionary, and the number of words in the list.
Next N line, each line has a string Wi, represents the ith word in the dictionary (0<|Wi|100000)
Next Q line, each line has two string Pi , Si, represents the prefix and suffix of the ith word in the list (0<|Pi|,|Si|100000,0<|Pi|+|Si|100000)
All of the above characters are lowercase letters.
The dictionary does not contain the same words.

Limits
T5
0<N,Q100000
Si+Pi500000
Wi500000
 

Output
For each test case, output Q lines, an integer per line, represents the answer to each word in the list.


Sample Input
1
4 4
aba
cde
acdefa
cdef
a a
cd ef
ac a
ce f
 


Sample Output
2
1
1
0



题意:
给你两个数字,N 和 Q ,表示字典里有 N 个单词,然后 Q 个询问,每个询问给你两个字符串 q 和 s ,q 表示前缀,s 表示后缀,让你在字典里找,符合条件的前缀和后缀的单词的个数。

分析:
字典树,本题对我来说是个非常好的题,首先从本题的建树来说,本题有两种建树方法,这是暑期多校联赛的一题,当时比赛的时候没有写出来,当时并不会字典树,过后是一个学长讲的这题,然而当时并不是很理解,这次专题练习时又遇见了,学姐又讲了一遍,因为本题有点特殊,平时是给你一些字符串,再给你一些前缀或者后缀让你查,但本题是既给了前缀又给了后缀,让你找副歌条件的字符串的个数,怎样建树是个难题,你想啊,你要是用字典里的字符串来建树,查的时候前缀可以很快查出来,但是后缀怎么衔接呢,中间空了多少字符我们也不确定,所以本题我们有两种建树的方法,我先说第一种,你可以以字典里的单词建树,但是单词的改变一下来建树,我们可以头尾交叉来建树,比如题中的 acdefa 这个单词,我们将它变成 aacfde来建树(从前找一个字符,从后找一个字符),然后用前后缀查询的时候,比如题中的 ac a 这个前后缀,我们可以将它也变成头尾交叉的方式,虽然我们不知道中间丢失的部分,但这并不影响,和建树是单词的变化保持一致,将 ac a 变成  aac 来查询,是不是发现这样就可以查到了呢,我们发现 aac 在 aacfde   中就可以被找到了,是不是很巧妙呢,哈哈哈哈!我们再来看一下第二种方法,也是我代码的方法(好吧,这个代码是借鉴学姐讲的时候的代码),这个方法使用前后缀来建树的,但是建树的时候,我们也做了点改变,比如 ac a 这个前后缀建树的时候,我们将它变成
ac#a 这样来建树,而 cd ef 这个前后缀则变成 cd#fe 来建树,发现什么了没有呢?用一个通配符来表示中间丢失的部分,然后将其后缀颠倒过来建树, 那么当我们查询 acdefa 这个词的时候,我们会在字典树里匹配到前两个字符 ac ,那么接下来我们会发现 c 节点的孩子节点有通配符,那么此时我们就应该将 acdefa 这个单词匹配的前缀去掉,然后将剩下的字符颠倒过来继续匹配,如果匹配到结子节点了,说明匹配成功了,这种方法是不是也很巧妙呢,哈哈哈哈哈哈。。。。。



#include<iostream>#include<stdio.h>#include<string>#include<string.h>#include<malloc.h>using namespace std;char w[500005],p[100005],s[100005];int id[100005],len[100005],ans[100005];/// w 数组表示的是一共的输入的字符串,p表示前缀,s表示后缀/// id[i] 数组很巧妙,表示第 i 个前后缀出现的编号///len[i]代表第i个字符串的长度///ans[i]是字典中含第i个前后缀的单词的数量struct Node{    int num;///该节点的编号    struct Node *son[27];///26个字符,多加一个通配符    Node()///节点的初始化    {        num=0;        for(int i=0;i<27;i++)            son[i]=NULL;    }};void Insert(Node *root,int num)///插入字符串,建字典树{    Node *q=root;    int k=0;    while(p[k]!='\0')    {        int a=p[k]-'a';        if(q->son[a]==NULL)            q->son[a]=new Node;        q=q->son[a];        k++;    }    /**接下来的操作是如果该节点是没有被访问过的,证明是该字符串是第一个    以该节点为尾的,那么将该字符串的编号赋给它,但如果有重复的前后缀询问    的话,我们在建树的时候肯定不可能建两个一样的路径,所以我们将后者的id数组    存入上一个和它一样的前后缀的编号**/    if(q->num==0)        q->num=num;    else        id[num]=q->num;}void Find(Node *root,int ws,int we)///查找{    Node *p=root;    int k=ws;    while(p!=NULL&&k<=we)///如果该节点不是NULL并且字符串没到结尾    {        int a=w[k]-'a';        if(p->son[a]==NULL)///匹配失败            return ;        p=p->son[a];        if(p->son[26]!=NULL)        ///如果该节点的孩子节点有通配符,说明我们要颠倒字符串,再查询了        {            Node *temp=p->son[26];            for(int i=we;i>k;i--)            {                a=w[i]-'a';                if(temp->son[a]==NULL)///匹配失败                    break;                temp=temp->son[a];                if(temp->num)                    ans[temp->num]++;            }        }        k++;    }}void Free(Node *root)///释放字典树{    if(root!=NULL)    {        for(int i=0;i<=26;i++)            if(root->son[i]!=NULL)                Free(root->son[i]);    }    free(root);}int main(){    int t,n,m,len1,len2;    scanf("%d",&t);    while(t--)    {        scanf("%d %d",&n,&m);        Node *root=new Node;        int prelen=0;        /**接下来这个for循环厉害了,平时我们怎么输入字符串的,这样的输入字符串        我是第一次,这就是相当于说 w 数组是一个长长的数组,然后你先输入第一个        字符串,然后接着第一个字符串后面输入第二个字符串,w 数组存的是所有        字符串**/        for(int i=0;i<n;i++)        {            scanf("%s",w+prelen);            len[i]=strlen(w+prelen);            prelen+=len[i];        }        for(int i=1;i<=m;i++)        {            id[i]=i;///初始id数组里存的都是自己原本的编号            ans[i]=0;///这个数组都刷为0        }        for(int i=1;i<=m;i++)///开始输入前后缀了        {            scanf("%s",p);///前缀            len1=strlen(p);            p[len1++]='a'+26;///前缀后面加上通配符            scanf("%s",s);///后缀            len2=strlen(s);            for(int j=len2-1;j>=0;j--)///将后缀颠倒加在通配符后面                p[len1++]=s[j];            p[len1]='\0';///字符串的结束标志            Insert(root,i);///插入,建树        }        prelen=0;        for(int i=0;i<n;i++)        {            Find(root,prelen,prelen+len[i]-1);///查找,该单词的头尾的位置都要传入            prelen+=len[i];        }        /**这里注意啦,我们以id里存的标号为主,因为在插入的时候,如果遇见        相同的前后缀,我们将统一统一用最先出现的前后缀的编号,因为统计起来        都是一样的,如此巧妙的数组**/        for(int i=1;i<=m;i++)            printf("%d\n",ans[id[i]]);        Free(root);    }    return 0;}







原创粉丝点击