您的位置:首页 > 其它

HDU - 2222 Keywords Search (AC自动机)

2014-08-08 22:13 381 查看
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2222

题意:

给出n个单词,最后给出一字符串,需要找出该字符串里面出现了多少个给出的单词

每个单词出现只记录一次,例:给出单词:ab,abc。字符串为:ababab。 答案为: 1

给出的单词可以相同,例:给出单词:ab,ab,ab。字符串为:ab。答案为:3

分析:

典型的ac自动机问题

再Trie树的基础上,加一个失败指针(fail指针)即可,它指向是,若当前匹配失败后,节点跳转的位置。初始为根节点root

例如:

目前Trie树里面已有单词:abb,bc, 当前字符串为:abc。

开始从第一个单词匹配下去,当匹配到root->a->b->b,匹配失败,

然后从上一个匹配成功的地方root->a->b中的b节点开始跳转,跳转的目标是第二个单词root->b->c中的b节点

接着向下匹配,root->b->c成功,答案为1

所以,可以看出Trie树种的两条路径root->a->b->c, 和root->b->c,

第一条路径中的b节点的fail指针指向第二条路径的b节点

第二路径的b节点fail指针指向root指针...

一般的,当前路径节点上的失败指针fail,所指向的路径向上看,是当前路径的后缀,例如b 是ab后缀,所以建立Fail可以用BFS

代码:

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <string>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <map>

using namespace std;

#define MAX 1000000+10
#define MIN -1e5
#define INF 0x7f7f7f7f

int t, n, m;

char str[MAX];

struct Trie
{
int id; // 单词出现次数
Trie *child[26]; // 连接下一个字母节点指针
Trie *fail; // 匹配失败的指针
}root, trie[MAX]; // 根节点, 静态分配节点的数组

int t_num; // 静态数组下标

void insert(char str[]) // 插入单词, 构建Trie树
{
Trie *x = &root;
for(int i = 0; str[i]; i++)
{
if(!x->child[str[i]-'a']) // 出现新节点, 初始化
{
trie[t_num].id = 0;
trie[t_num].fail = &root;
memset(trie[t_num].child, 0, sizeof(trie[t_num].child));

x->child[str[i]-'a'] = &trie[t_num++];
}
x = x->child[str[i]-'a']; // 指向下一个字母
}
x->id++; // 完整的一个单词 +1
}

void setFail(Trie *x, int p) // 设置单个节点(x->child[p])的失败指针fail
{
// 当前节点(x->child[p])的fail指针,从双亲(x)的fail指针向下查找
Trie *y = x->fail;
// 一直找到所有双亲的fail查找出,子节点含有p的节点, 或者到达根节点--root
while(y != &root && !y->child[p])
{
y = y->fail;
}
// 当前双亲的fail中,子节点含有所求字母 并且 当前节点不是原节点, 则设置fail成功
if(y->child[p] && y->child[p] != x->child[p])
{
x->child[p]->fail = y->child[p];
}
}

void buildFail() // 建立整棵树的fail指针,BFS
{
Trie *x = &root;
// 因为当前单词的fail指针指向的新单词,为当前单词的后缀,所以新单词长度一定小于当前单词
// 因此,建立的顺序,从短到长,运用BFS进行宽度方式,逐个设置fail
queue<Trie*> q;
q.push(x);
while(!q.empty())
{
x = q.front();
q.pop();
for(int i = 0; i<26; i++) // Trie树每一层节点都遍历一遍
{
if(x->child[i]) // 存在节点的, 逐个设置它们的fail指针
{
setFail(x, i);
q.push(x->child[i]);
}
}
}
}

void query() // 查找字符串str中,出现的单词数(每个单词只计算一次)
{
Trie *x = &root;
int ans = 0;
for(int i =0; str[i]; i++)
{
while(x != &root && !x->child[str[i]-'a'])
{
x = x->fail;
}
if(!x->child[str[i]-'a']) continue; // 不存在当前节点, 跳过

x = x->child[str[i]-'a']; // 存在,则指向新节点,并计算
Trie *y = x;
// 把存在的当前字母的所有节点,通过fail查找一遍
// 查找过的标为-1,不再查找(不是完整单词的id为0,不影响结果)
while(y != &root && y->id != -1)
{
ans += y->id;
y->id = -1;
y = y->fail;
}
}
printf("%d\n", ans);
}

int main()
{
int i, j;
freopen("a.txt", "r", stdin);

scanf("%d", &t);
while(t--)
{
t_num = 0;
root.fail = &root;
memset(root.child, 0, sizeof(root.child));

scanf("%d", &n);
char word[50+10];
for(i = 0; i<n; i++)
{
scanf("%s", word);
insert(word);
}

buildFail();
scanf("%s", str);
query();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: