题目大意:
随着信息爆炸时代的到来所搜引擎进入我们每一个人的生活,比如向Google、百度之类的所搜引擎等。
Wiskey也向将所搜引擎的功能加入到他的图像检索系统中,每一个图像都有自己的描述,当用户键入图像描述的关键词后,系统会检索出拥有能匹配最多关键词的描述的图像并返回给用户,为了简化问题,大致目标就是在一篇文章中(即一段描述中)检索出指定的字符串。
现有多个测例(测例数题中给出),在每个测例中都会指定检索的字符串的数量N(N ≤ 10,000),并给出待检索的字符串,左后给出一篇文章(即描述),其中待检索字符串的长度不超过50,描述的长度1,000,000,对于每个测例都打印出在文章中出现了多少待检测的字符串(如果同一个字符串出现多次也只算一次)。
注释代码:
/*
* Problem ID : HDU 2222 Keywords Search
* Author : Lirx.t.Una
* Language : C++
* Run Time : 812 ms
* Run Memory : 28720 KB
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
//alphabet length
//字母表长
#define ALPHN 26
//maximum word length
//检索的最大模式串长度
#define MAXWDLEN 51
//maximum text length
//最大的文章长度
#define MAXTXTLEN 1000001
//maximum state number
//自动机中状态的最大数量
#define MAXSTATN 250001
using namespace std;
struct Node {//trie树中的结点
//count
//对单词进行计数,和普通trie树中的用法一样
//如果cnt=0则表示该状态不是模式字符串的结尾
//如果cnt>0表示该状态是模式字符串的结尾同时该字符串出现过多次
//可能会有这种蛋疼的用户,他查找的关键字重复输入多次
//对于这种情况,如果在文章中出现该单词则由于输入多次的原因出现次数
//也会相应地累加
//因此这里用cnt而不是flag,cnt起到一举两得的作用(不仅可以表示是否为
//字符串结尾,也能表示该单词出现了多少次)
int cnt;
//产生式的退出符号,即s --(a)--> r,即s -> ar,s和r分别为两种状态,a为终止符
//s、r为非终止符
//即stat[s].out[a] = r
//因此out就相当于产生符号->
int out[ALPHN];//总共可以有26中产生式,弧上式不同的26个字母
//fail指针,和KMP的next数组相似
//当自动机中的指针(即状态s)和text中的当前字符不匹配时就会利用该指针
//回退到和当前状态具有相同输入弧的最近状态,例如:
//s -(a)-> s1
// -(b)-> s2 -(a)-> s3
//则s3的fail指针就指向s1
int fail;
};
//对于KMP算法只能一次匹配一个模式串
//而对于AC自动机一次可以匹配多个模式串
//这里只不过是对检索的模式串利用trie树建立AC自动机
//然后将主串(就是所谓的文章或描述)放在AC自动机上扫描
//看看能否扫描到指定的模式串,并记录相关信息
char w[MAXWDLEN];//word,即模式串
char t[MAXTXTLEN];//text,即主串(文章)
//state,即自动机中的状态,形状为一trie树
//只不过就是在trie树中每个状态都加一个fail指针就变成了AC自动机
Node stat[MAXSTATN];
int tsn;//total state number,当前的总状态数,起始状态下标为0
void
insert(char *w) {//插入,即普通的trie树插入
int s;
int aph;
s = 0;
while ( *w ) {
aph = *(w++) - 'a';
if ( !stat[s].out[aph] ) {
stat[s].out[aph] = ++tsn;
s = tsn;
continue;
}
s = stat[s].out[aph];
}
stat[s].cnt++;//注意,如果用户蛋疼的重复输入检索字符串则需要++
}
void
bd_AC_auto(void) {//build AC automator
//建立AC自动机
//其实建立AC自动机的第一步已经有insert完成了,即构造一棵
//初始的trie树
//这个函数完成的是为状态添加fail指针
queue<int> q;//queue队列
//state,即自动机中的状态,形状为一trie树
//只不过就是在trie树中每个状态都加一个fail指针就变成了AC自动机
int s, ss, r;
int aph;//alpha,即当前检查的字母
//初始化
//将其实状态所能推出的状态入队
//并将这些推出的第一层状态的fail指针指向初状态0
//因为调用此函数之前已经memset(stat, 0, sizeof(stat))过了
//因此将fail=0的语句可以省略
for ( aph = 0; aph < ALPHN; aph++ )
if ( ss = stat[0].out[aph] )
q.push(ss);
//建立自动关机的核心算法
while ( !q.empty() ) {
s = q.front();//从队列中获取一个当前状态
q.pop();
for ( aph = 0; aph < ALPHN; aph++ )//检查可从当前状态推出的所有子状态
if ( ss = stat[s].out[aph] ) {
r = s;
//方法是先找到当前状态最近的并且可以推出ss状态的fail状态
while ( r = stat[r].fail )
if ( stat[r].out[aph] ) {
//如果能找到则直接将该fail状态的-(aph)->状态赋给ss的fail
stat[ss].fail = stat[r].out[aph];
break;
}
if ( !r )//否则就代表fail一直回溯到了初始状态
if ( stat[0].out[aph] )//但是有可能初始状态可以推出ss(这种情况很容易忽略!!!)
stat[ss].fail = stat[0].out[aph];
else//如果初始状态也推不出ss则只能让ss的fail指向初始状态了
//可以这样理解,初始状态可以由任意字母推出,因此如果任意状态fail都可以回溯到初始状态
stat[ss].fail = 0;
q.push(ss);//由于s->ss存在所以要将ss入队待下次扩展
}
}//如此一来,trie树中所有状态都建立的fail指针,因此自动机建立完毕
}
int
query(char *t) {//查询,即在文章t中查询模式字符串
int cnt;
int s, r;//当前状态和回溯状态(即fail指向的状态)
int aph;//同上以个函数
cnt = 0;//匹配到的数量
s = 0;
while ( *t ) {
aph = *(t++) - 'a';
//如果可从当前状态通过当前检查的字母aph推不出任何状态
//则只能通过fail回退,直到能推出状态为止
while ( !stat[s].out[aph] && s )
s = stat[s].fail;
//两种可能
//一种是找到可通过aph推出状态的s(有可能回溯到了初始状态)
//另一种是回溯到初始状态但不能推出任何状态,这种情况s就只能停留在0了
//就和KMP中j回溯到0也无法使mod[j + 1] == pri[i]的情形一样
if ( !( s = stat[s].out[aph] ) )
continue;//直接检查下一个主串字符
//表示当前主串字符匹配成功,并且找到了匹配的状态s
//现在对s以及s的fail状态进行检查
//因为s的fail状态都和s一样具有相同的输入弧,因此都需要检查
r = s;//r为回溯状态
while ( r ) {
if ( stat[r].cnt ) {//如果r为单词结尾
cnt += stat[r].cnt;//则累加
//由于只记录出没出现并不记录出现几次
//因此记录完后就得清零,否则可能会重复计算
stat[r].cnt = 0;
}
r = stat[r].fail;//当前状态检查好后再往前回溯检查上一级fail状态
}
}
return cnt;
}
int
main() {
int tc;//test case,测例数
int n;//模式串数量
scanf("%d", &tc);
while ( tc-- ) {
tsn = 0;
memset(stat, 0, sizeof(stat));
scanf("%d", &n);
while ( n-- ) {
scanf("%s", w);
insert(w);
}
bd_AC_auto();
scanf("%s", t);
printf("%d\n", query(t));
}
return 0;
}
无注释代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define ALPHN 26
#define MAXWDLEN 51
#define MAXTXTLEN 1000001
#define MAXSTATN 250001
using namespace std;
struct Node {
int cnt;
int out[ALPHN];
int fail;
};
char w[MAXWDLEN];
char t[MAXTXTLEN];
Node stat[MAXSTATN];
int tsn;
void
insert(char *w) {
int s;
int aph;
s = 0;
while ( *w ) {
aph = *(w++) - 'a';
if ( !stat[s].out[aph] ) {
stat[s].out[aph] = ++tsn;
s = tsn;
continue;
}
s = stat[s].out[aph];
}
stat[s].cnt++;
}
void
bd_AC_auto(void) {
queue<int> q;
int s, ss, r;
int aph;
for ( aph = 0; aph < ALPHN; aph++ )
if ( ss = stat[0].out[aph] )
q.push(ss);
while ( !q.empty() ) {
s = q.front();
q.pop();
for ( aph = 0; aph < ALPHN; aph++ )
if ( ss = stat[s].out[aph] ) {
r = s;
while ( r = stat[r].fail )
if ( stat[r].out[aph] ) {
stat[ss].fail = stat[r].out[aph];
break;
}
if ( !r )
if ( stat[0].out[aph] )
stat[ss].fail = stat[0].out[aph];
else
stat[ss].fail = 0;
q.push(ss);
}
}
}
int
query(char *t) {
int cnt;
int s, r;
int aph;
cnt = 0;
s = 0;
while ( *t ) {
aph = *(t++) - 'a';
while ( !stat[s].out[aph] && s )
s = stat[s].fail;
if ( !( s = stat[s].out[aph] ) )
continue;
r = s;
while ( r ) {
if ( stat[r].cnt ) {
cnt += stat[r].cnt;
stat[r].cnt = 0;
}
r = stat[r].fail;
}
}
return cnt;
}
int
main() {
int tc;
int n;
scanf("%d", &tc);
while ( tc-- ) {
tsn = 0;
memset(stat, 0, sizeof(stat));
scanf("%d", &n);
while ( n-- ) {
scanf("%s", w);
insert(w);
}
bd_AC_auto();
scanf("%s", t);
printf("%d\n", query(t));
}
return 0;
}
优化:
/*
* Problem ID : HDU 2222 Keywords Search
* Author : Lirx.t.Una
* Language : C++
* Run Time : 890 ms
* Run Memory : 28712 KB
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define AN 26
#define MAXM 50
#define MAXT 1000000
#define MAXS 250000
using namespace std;
struct Node {
int out[AN];
int prv;
int type;
};
Node stat[MAXS];
char m[MAXM + 1];
char t[MAXT + 1];
int tsn;
void
insert(char *m) {
int s;
int a;
s = 1;
while ( *m ) {
a = *m++ - 'a';
if ( !stat[s].out[a] ) {
stat[s].out[a] = ++tsn;
s = tsn;
continue;
}
s = stat[s].out[a];
}
stat[s].type++;
}
void
build(int n) {
int s, ss, r;
int a;
queue<int> q;
memset(stat, 0, sizeof(stat));
for ( a = 0; a < AN; a++ ) stat[0].out[a] = 1;
tsn = 1;
q.push(1);
while ( n-- ) {
scanf("%s", m);
insert(m);
}
while ( !q.empty() ) {
s = q.front();
q.pop();
for ( a = 0; a < AN; a++ )
if ( ss = stat[s].out[a] ) {
q.push(ss);
r = s;
while ( true )
if ( stat[ r = stat[r].prv ].out[a] ) {
stat[ss].prv = stat[r].out[a];
break;
}
}
}
}
int
run(char *t) {
int cnt;
int s, r;
int a;
cnt = 0;
s = 1;
while ( *t ) {
a = *t++ - 'a';
while ( !stat[s].out[a] ) s = stat[s].prv;
if ( !s ) { s = 1; continue; }
s = stat[s].out[a];
r = s;
while ( r != 1 ) {
if ( stat[r].type ) {
cnt += stat[r].type;
stat[r].type = 0;
}
r = stat[r].prv;
}
}
return cnt;
}
int
main() {
int tst;
int n;
scanf("%d", &tst);
while ( tst-- ) {
scanf("%d", &n);
build(n);
scanf("%s", t);
printf("%d\n", run(t));
}
return 0;
}
单词解释:
Wiskey:人名,酒名,威士忌
retrieval:n, 检索