题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6096
题目意思:
输入第一个T,代表测试数据数组。
输入n,q。n代表将要给出n个字符串,q是q次查询。
q次查询每次输入的是一组前缀和后缀。
针对每一次询问,问n个字符串中,有多少个字符串是以当前询问的前缀开头,后缀结尾。
注意前缀和后缀是不交叉的。
比赛的时候不会写。后来看了好多人的博客。
有字典树写的又有AC自动机写的,AC自动机写的代码简直太长了,看不明白。我AC自动机才
刚入门,看不懂,太弱了。
我就寻找用字典树写的。用字典树的有好多人都是那样写的。处理n个字符串的时候,建立字典树,
按照前面的字符,后面的字符这样交替存储,但是这样的话建立字典树的时候需要建立很多
节点,我看的有些代码,压根提交就没过,用这种方法,选择动态链表建立字典树的时候
由于每次都要动态分配空间,最后提交超时了,选择静态链表建立字典树,很不幸超内存了。
后来我在别人博客看到了也是字典树的方法,但是比上面的好理解多了,个人认为。而且要
建的节点数也少得多,我用动态链表建立字典树就AC了。
思路来源:http://blog.csdn.net/hnust_derker/article/details/77088157
其思路是这样的,建立字典树不在根据n个字符串来建立树,而是根据Q次输入的前缀和后缀的组合
来建立字典树。例如:对于cd ef这个询问:我们转换成这样:cd#fe。即前缀不变,前缀后面加上不是小写字母
的字符,这个字符代表通配符,就像数据库中我们可以用的通配符一样。#表示可以匹配任意字符。当然在代码
中我们并不会用#去作为通配符,而是用'a'+26这个字符‘{’来代表通配符,这样的话,每个字典树节点的儿子数组
开27就够了,刚好可以存储'a'+26。用'#'我们就要有于'#'-‘a'对应的位置去存储他,这样很浪费空间。对于cd ef这
样的询问,我们把它转换成 cd{fe 插入到字典树。
字典树的节点里面有两个成员,一个就是son[27] 分别用来映射字符a~z,和通配符’{‘,'{'在ASCII表中的位置
在'z'之后。另一个成员就是num,当插入完成cd{fe后,最后一个节点的num我们赋值称为当前查询的编号。意识
是这个节点所代表的前缀后缀的组合是对应于第i个询问的。接下来我们就要看这n个字符串对字典树中的前后缀
的贡献了。
假如我们当前的n个字符串里面有cdabef这个字符串,当在字典树中查询的时候,我们会查询到c,然后看c后面有没有通配符,发现没有,继续往后查询cd,发现cd后来有通配符’{‘这个时候,我们说明前缀已经匹配了。然后我们需要倒着来匹配所查询的字符串看看后缀存不存在。也就是看feba来匹配字典树上的后缀。可以知道能够匹配上fe.那么那么我们要把所有匹配上的后缀它所代表的查询的位置都加上1。
总体思路就是,每次扩展前缀,发现通配符,我们去倒着匹配后缀。循环此过程。
想不明白的话,看看这个数据:字典树中有cd{fe 和 cda{fe,然后当前给出的cdabef
处理到cd的时候,d后面发现有通配符,然后去匹配后缀。匹配完后,我们扩展前缀,即
考虑cda发现a后面有通配符,然后去匹配后缀。这样cdabef对cd ef 和 cda ef这两个查询都贡献了一次。
有一点非常坑,题目给出的Q次查询中会有重复的出现,对于两个给出的完全相同的前缀和后缀的组合
它在插入字典树最后所到达的节点P必定是相同的。如果p->num发现不是0,说明之前已经有一个前缀后缀
的组合到达这个位置了,现在再次到达这个位置,p->num只统计最早到达p节点的前后缀组合就行,后到达
的和之前到达的一样,当然最后输出的时候答案也是一样的。我们可以用一个id[]数组,原来第i次询问的
id[i] = i就行了,如果第i个询问和第j个询问是相同的,他们最终答案一样,我们让id[j] = i;就可以了。
这样我们最后输出ans[id[i]]就是答案了。
具体详情看代码:
动态链表写法
AC代码:
#include <iostream>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
const int maxn = 27;
const int maxstr = 1e7;
char str[maxstr],s1[maxstr],s2[maxstr];
int id[maxstr];
int len[maxstr]; ///len[i]代表第i个字符串的长度。
int ans[maxstr]; ///ans[i]是第i个最后输出的满足第i个询问的字符串数量
typedef struct TrieNode ///字典树节点
{
int num; ///该节点所代表的查询的编号。
struct TrieNode *son[maxn]; ///26个字母加上一个通配符。
}Trie;
Trie* createNode() ///创建节点的函数
{
Trie *node;
node = (Trie*)malloc(sizeof(Trie));
for(int i = 0; i < maxn; i++)
node->son[i] = NULL;
node->num = 0;
return node;
}
void insertWord(Trie *root,int index) ///将第index个询问插入字典树。
{
Trie *p; ///辅助指针
p = root;
int i = 0;
while(s1[i] != '\0')
{
int lowercase = s1[i]-'a'; ///求小写字母对应的整数
if(p->son[lowercase]==NULL)
{
p->son[lowercase] = createNode();
}
p = p->son[lowercase];
i++;
}
if(p->num == 0) ///还没有询问到达这里
p->num = index;
else id[index] = p->num; ///遇到相同的数据只存最早出现的那个查询的编号。
}
void query(Trie *root,int strbegin,int strend)
{
Trie *p,*tmp;
p = root;
int i = strbegin;
while(p!=NULL && i<=strend)
{
int lowercase = str[i]-'a';
if(p->son[lowercase] == NULL) return; ///前缀匹配的时候就失配了
p = p->son[lowercase];
if(p->son[26]!=NULL) ///发现通配符,考虑当前前缀。然后匹配后缀
{
tmp = p->son[26]; ///现在tmp是通配符,我们继续往后匹配后缀
for(int j=strend; j > i; j--)
{
lowercase = str[j]-'a';
if(tmp->son[lowercase]==NULL) ///失配边跳出
break;
tmp = tmp->son[lowercase];
if(tmp->num)
ans[tmp->num]++;
}
}
i++; ///扩展前缀,然后继续找通配符,去匹配后缀
}
}
void free_Trie(Trie *root) ///释放内存,没用了就换给系统,不然内存会爆的。
{
if(root != NULL)
{
for(int i = 0; i < maxn; i++)
if(root->son[i]!=NULL)
free_Trie(root->son[i]);
}
free(root);
}
int main()
{
int t;
scanf("%d",&t); ///t组测试数据
while(t--)
{
int n,q; ///n个字符,q次查询
scanf("%d%d",&n,&q);
Trie *root;
root = createNode();
int prelen = 0; ///当前串长
for(int i = 0; i < n; i++) ///这里妙啊,不明白的可以str输出,肯定一看就秒懂。
{
scanf("%s",str+prelen);
len[i] = strlen(str+prelen);
prelen += len[i];
}
for(int i = 1; i <= q; i++)
{
id[i] = i;
ans[i] = 0;
}
for(int i = 1; i <= q; i++)
{ ///s1是前缀,s2是后缀,把s2反转,然后组成:前缀+通配符+后缀的反转。然后插入字典树。
scanf("%s",s1);
int len1 = strlen(s1);
s1[len1++] = 'a'+26; ///'{'
scanf("%s",s2);
int len2 = strlen(s2);
for(int j = len2-1; j >= 0; j--)
{
s1[len1++] = s2[j];
}
s1[len1] = '\0';
insertWord(root,i);
}
prelen = 0;
for(int i = 0; i < n; i++)
{
query(root,prelen,prelen+len[i]-1);
prelen += len[i];
}
for(int i = 1; i <= q; i++)
printf("%d\n",ans[id[i]]);
free_Trie(root);
}
return 0;
}
静态数组写法:
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long LL;
const int maxn = 6e5+10;
using namespace std;
int node[maxn][27]; ///node[i]是节点。二维开27是因为还需要一个通配符。
char str[maxn],s1[maxn],s2[maxn];
int len[maxn]; ///len[i]存放第i个字符串的长度。
int ans[maxn]; ///ans[i]用来存放以第i个查询为前后缀的字符串有多少个。
int num[maxn];
int id; ///节点编号
int nex[maxn];
void insertWord(int i)
{
int p = 0;
int index=0;
while(s1[index]!='\0')
{
int lowercase = s1[index]-'a';
if(node[p][lowercase]==0) ///这个字母不存在
{
memset(node[id],0,sizeof(node[id]));
num[id] = 0;
node[p][lowercase] = id++; ///存放节点的编号
}
p = node[p][lowercase]; ///P指向子节点
index++;
}
if(num[p]==0)
{
num[p] = i;
}
else
{
nex[i] = num[p];
}
}
///传入要查询字符串在str数组中下标的起点和终点。
void query(int strbegin,int strend)
{
int p = 0; ///根节点编号永远都是0
for(int i = strbegin; i <= strend; i++)
{
int lowercase = str[i]-'a';
if(node[p][lowercase]==0) return; ///前缀都匹配不上的直接pass.
p = node[p][lowercase];
if(node[p][26]==0) continue; ///发现当前节点所代表的前缀后面没有统配符。
int tmp = node[p][26]; ///tmp为通配符。开始倒着匹配后缀
for(int j = strend; j > i; j--)
{
lowercase = str[j]-'a';
tmp = node[tmp][lowercase];
if(tmp==0) ///失配
break;
if(num[tmp]) ans[num[tmp]]++;
}
}
}
int main()
{
int t; ///测试数据组数
scanf("%d",&t);
while(t--)
{
id = 1; ///节点编号从1开始。
memset(node[0],0,sizeof(node[0])); ///把根节点的孩子节点置为空
memset(ans,0,sizeof(ans));
num[0] = 0;
int n,q;
scanf("%d%d",&n,&q); ///n个字符串,q次查询。
for(int i = 1; i <= q; i++)
nex[i] = i;
int number = 0;
///每次把当前输入的字符串链接到之前输入的字符串后面。
for(int i = 0; i < n; i++)
{
scanf("%s",str+number);
len[i] = strlen(str+number);
number += len[i];
}
for(int i = 1; i <= q; i++)
{
scanf("%s",s1);
int len1 = strlen(s1);
s1[len1++] = 'a'+26; ///放入一个通配符。
scanf("%s",s2); ///将s2反转放入s1后面
int len2 = strlen(s2);
for(int j = len2-1; j >= 0; j--)
{
s1[len1++] = s2[j];
}
s1[len1] = '\0';
insertWord(i); ///现在插入字典树的是第i个前缀和后缀组合
}
number = 0;
for(int i = 0; i < n; i++) ///查询n个字符串
{
///第i个字符串的长度为len[i].
query(number,number+len[i]-1);
number += len[i];
}
for(int i = 1; i <= q; i++)
printf("%d\n",ans[nex[i]]);
}
return 0;
}