AC自动机 - 多模式串匹配问题的基本运用 + 模板题 --- HDU 2222
Keywords Search
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 35655 Accepted Submission(s): 11496
Wiskey also wants to bring this feature to his image retrieval system.
Every image have a long description, when users type some keywords to find the image, the system will match the keywords with description of image and show the image which the most keywords be matched.
To simplify the problem, giving you a description of image, and some keywords, you should tell me how many keywords will be match.
Each case will contain two integers N means the number of keywords and N keywords follow. (N <= 10000)
Each keyword will only contains characters 'a'-'z', and the length will be not longer than 50.
The last line is the description, and the length will be not longer than 1000000.
Mean:
给你n个单词,再给你一篇文章,让你统计有多少个单词在文章中出现过。
analyse:
裸的AC自动机,模板题。
Time complexity:o(n)+o(ml) n个模式串长度均不超过m,文本串长度为L
Source code:
// Memory Time
// 1347K 0MS
// by : Snarl_jsb
// 2014-09-29-20.14
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<string>
#include<climits>
#include<cmath>
#define N 10010
#define LL long long
using namespace std; namespace ac_auto
{
char str[1000005];
struct node
{
node *next[26];
node *fail;
int count;
node()
{
for(int i = 0; i < 26; i++)
next[i] = NULL;
count = 0;
fail = NULL;
}
}*q[50*N];
node *root;
int head, tail; void Insert(char *str) // 插入单词
{
node *p = root;
int i = 0, index;
while(str[i]) {
index = str[i] - 'a';
if(p->next[index] == NULL)
p->next[index] = new node();
p = p->next[index];
i++;
}
p->count++;
}
void build_ac_automation(node *root) // bfs建立fail指针
{
root->fail = NULL;
q[tail++] = root;
while(head < tail) {
node *temp = q[head++];
node *p = NULL;
for(int i = 0; i < 26; i++) {
if(temp->next[i] != NULL) {
if(temp == root) temp->next[i]->fail = root;
else {
p = temp->fail;
while(p != NULL) {
if(p->next[i] != NULL) {
temp->next[i]->fail = p->next[i];
break;
}
p = p->fail;
}
if(p == NULL) temp->next[i]->fail = root;
}
q[tail++] = temp->next[i];
}
}
}
}
int Query(node *root) // 匹配 + 统计
{
int i = 0, cnt = 0, index;
node *p = root;
while(str[i]) {
index = str[i] - 'a';
while(p->next[index] == NULL && p != root) p = p->fail;
p = p->next[index];
if(p == NULL) p = root;
node *temp = p;
while(temp != root && temp->count != -1) {
cnt += temp->count;
temp->count = -1;
temp = temp->fail;
}
i++;
}
return cnt;
}
}
using namespace ac_auto; int main()
{
int T, n;
scanf("%d",&T);
while(T--)
{
head = tail = 0; // 清零
root = new node(); // 申请新的root结点
scanf("%d",&n);
while(n--)
{
scanf("%s", str);
Insert(str); // 插入单词
}
build_ac_automation(root); // 建树
scanf("%s",str);
printf("%d\n", Query(root)); // 查找+统计
}
return 0;
}
附上注释版(博主很贴心的有木有=.=)
#include<cstdio> const int N = 10010;
char str[1000005];
struct node
{
node *next[26]; // 每个结点都对应26个字母的指针
node *fail; // 失配指针
int count; //
node() // 构造函数初始化
{
for(int i = 0; i < 26; i++)
next[i] = NULL;
count = 0;
fail = NULL;
}
}*q[50*N];
node *root;
int head, tail; void Insert(char *str) // 插入单词.相当于构建一个Trie树
{
node *p = root;
int i = 0, index;
while(str[i]) {
index = str[i] - 'a'; // 转化为相对数字来存
if(p->next[index] == NULL) // 该字母未插入过
p->next[index] = new node(); // 为该字母申请一个结点
p = p->next[index]; // 移至下一个
i++;
}
p->count++; // 记录该结点的单词总共插入的次数
}
void build_ac_automation(node *root) // bfs建立fail指针
{
root->fail = NULL;
q[tail++] = root;
while(head < tail) {
node *temp = q[head++];
node *p = NULL;
for(int i = 0; i < 26; i++) {
if(temp->next[i] != NULL) {
if(temp == root) temp->next[i]->fail = root;
else {
p = temp->fail;
while(p != NULL) {
if(p->next[i] != NULL) {
temp->next[i]->fail = p->next[i];
break;
}
p = p->fail;
}
if(p == NULL) temp->next[i]->fail = root;
}
q[tail++] = temp->next[i];
}
}
}
} int Query(node *root) // 匹配 + 统计
{
int i = 0, cnt = 0, index;
node *p = root;
while(str[i])
{
index = str[i] - 'a';
while(p->next[index] == NULL && p != root) //前缀是相同的,所以不管哪个指针走到了count不为0的结点上,那么该结点所代表的单词就匹配成功
p = p->fail;//失配情况下,p指针指向p->fail.(相当于KMP的next数组)
p = p->next[index];//由于现在所在的位置是父节点,所以需要向下移动一个位置
if(p == NULL)
p = root; //如果匹配失败,移动到root,重新开始匹配
node *temp = p;//
while(temp != root && temp->count != -1) //统计--如果匹配成功,那么count>1,表示该结点代表的单词数量;否则表示该结点没有单词
{
cnt += temp->count; //统计该单词出现的次数
temp->count = -1; //标记为-1,表示该单词已经加入了cnt中,下次就不用重复统计
temp = temp->fail;//判断整条链上的匹配情况
}
i++;
}
return cnt;
} int main()
{
int T, n;
scanf("%d",&T);
while(T--)
{
head = tail = 0; // 清零
root = new node(); // 申请新的root结点
scanf("%d",&n);
while(n--)
{
scanf("%s", str);
Insert(str); // 插入单词
}
build_ac_automation(root); // 建树
scanf("%s",str);
printf("%d\n", Query(root)); // 查找+统计
}
return 0;
}
后来交了一发G++才发现会超内存,毕竟每次Trie树申请的内存没释放,内存开销还是有点大。同样交C++就没事,说明C++的内存回收机制比G++要好很多。
但是自己写了个手动释放内存的函数也还是无济于事。后来又把BFS的静态队列改为了动态队列,同样也没多大作用。
/*
* this code is made by crazyacking
* Verdict: Accepted
* Submission Date: 2015-07-17-13.54
* Time: 0MS
* Memory: 137KB
*/
#include <queue>
#include <cstdio>
#include <set>
#include <string>
#include <stack>
#include <cmath>
#include <climits>
#include <map>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#define LL long long
#define ULL unsigned long long
using namespace std;
const int N = 10010;
const int M = 10 + int( 1e6 );
class Node
{
public:
int cnt;
Node *next[26], *fail;
Node()
{
cnt = 0;
for( int i = 0; i < 26; ++i ) next[i] = NULL;
fail = NULL;
}
};
queue<Node*> q;
//Node *q[50 * N];
Node *root;
int head, tail;
char str[M]; void Insert( char *str ) // build Trie-Tree
{
Node *p = root;
int i = 0, index;
while( str[i] )
{
index = str[i] - 'a';
if( p->next[index] == NULL )
p->next[index] = new Node();
p = p->next[index];
++i;
}
p->cnt++;
} void build_ac_automation( Node *root ) // build fail ptr
{
root->fail = NULL;
while(!q.empty()) q.pop();
q.push(root);
// q[tail++] = root;
while( !q.empty() )
{
Node *temp=q.front();
q.pop();
// Node *temp = q[head++];
Node *p = NULL;
for( int i = 0; i < 26; ++i )
{
if( temp->next[i] != NULL )
{
if( temp == root ) temp->next[i]->fail = root;
else
{
p = temp->fail;
while( p != NULL )
{
if( p->next[i] != NULL )
{
temp->next[i]->fail = p->next[i];
break;
}
p = p->fail;
}
if( p == NULL ) temp->next[i]->fail = root;
}
// q[tail++] = temp->next[i];
q.push(temp->next[i]);
}
}
}
} int Query( Node *root ) // mathing and count
{
int i = 0, ans = 0, index;
Node *p = root;
while( str[i] )
{
index = str[i] - 'a';
while( p->next[index] == NULL && p != root )
p = p->fail;
p = p->next[index];
if( p == NULL )
p = root;
Node *temp = p;
while( temp != root && temp->cnt != -1 )
{
ans += temp->cnt;
temp->cnt = -1;
temp = temp->fail;
}
i++;
}
return ans;
} /**< 开始交G++一直超内存,交C++就过了,看来C++回收内粗的机制做得不错。 */
/**< 手动释放内存也还会超,看来内存占用不在这儿,应该是BFS的静态队列 */
void free_malloc(Node *root)
{
queue<Node*> Q;
Q.push(root);
while(!Q.empty())
{
Node* p=(Q.front());
Q.pop();
for(int i=0;i<26;++i)
{
if(p->next[i]!=NULL)
{
Q.push((Node*) (p->next[i]));
}
}
delete(p);
}
root=NULL;
} int main()
{
ios_base::sync_with_stdio( false );
cin.tie( 0 );
int Cas;
scanf( "%d", &Cas );
while( Cas-- )
{
int n;
head = tail = 0;
root = new Node();
scanf( "%d", &n );
while( n-- )
{
scanf( "%s", str );
Insert( str );
}
build_ac_automation( root );
scanf( "%s", str );
printf( "%d\n", Query( root ) );
free_malloc(root);
}
return 0;
}
/* */
C++类的成员函数会对函数内new申请的空间进行回收,但是写了类依然逃脱不了G++超时的诅咒。
/*
* this code is made by crazyacking
* Verdict: Accepted
* Submission Date: 2015-07-17-16.17
* Time: 0MS
* Memory: 137KB
*/
#include <queue>
#include <cstdio>
#include <set>
#include <string>
#include <stack>
#include <cmath>
#include <climits>
#include <map>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#define LL long long
#define ULL unsigned long long
using namespace std; char str[1000005];
struct node
{
node *next[26];
node *fail;
int count;
node()
{
for( int i = 0; i < 26; i++ )
next[i] = NULL;
count = 0;
fail = NULL;
}
};
queue<node*> q;
node *root; class AC_AUTO_CLASS
{
public:
void Insert( char *str ) // 插入单词
{
node *p = root;
int i = 0, index;
while( str[i] )
{
index = str[i] - 'a';
if( p->next[index] == NULL )
p->next[index] = new node();
p = p->next[index];
i++;
}
p->count++;
}
};
void build_ac_automation( node *root ) // bfs建立fail指针
{
root->fail = NULL;
while( !q.empty() ) q.pop();
q.push( root );
while( !q.empty() )
{
node *temp = q.front(); q.pop();
node *p = NULL;
for( int i = 0; i < 26; i++ )
{
if( temp->next[i] != NULL )
{
if( temp == root ) temp->next[i]->fail = root;
else
{
p = temp->fail;
while( p != NULL )
{
if( p->next[i] != NULL )
{
temp->next[i]->fail = p->next[i];
break;
}
p = p->fail;
}
if( p == NULL ) temp->next[i]->fail = root;
}
q.push( temp->next[i] );
}
}
}
} int Query( node *root ) // 匹配 + 统计
{
int i = 0, cnt = 0, index;
node *p = root;
while( str[i] )
{
index = str[i] - 'a';
while( p->next[index] == NULL && p != root ) p = p->fail;
p = p->next[index];
if( p == NULL ) p = root;
node *temp = p;
while( temp != root && temp->count != -1 )
{
cnt += temp->count;
temp->count = -1;
temp = temp->fail;
}
i++;
}
return cnt;
} int main()
{
int T, n;
scanf( "%d", &T );
while( T-- )
{
root = new node(); // 申请新的root结点
scanf( "%d", &n );
{
AC_AUTO_CLASS ac_auto_class;
while( n-- )
{
scanf( "%s", str );
ac_auto_class.Insert( str ); // 插入单词
}
build_ac_automation( root ); // 建树
scanf( "%s", str );
printf( "%d\n", Query( root ) ); // 查找+统计
}
}
return 0;
}
AC自动机 - 多模式串匹配问题的基本运用 + 模板题 --- HDU 2222的更多相关文章
- AC 自动机——多模式串匹配
网站上的敏感词过滤是怎么实现的呢? 实际上,这些功能最基本的原理就是字符串匹配算法,也就是通过维护一个敏感词的字典,当用户输入一段文字内容后,通过字符串匹配算法来检查用户输入的内容是否包含敏感词. B ...
- AC自动机——多模式串匹配的算法思想
标准KMP算法用于单一模式串的匹配,即在母串中寻求一个模式串的匹配,但是现在又存在这样的一个问题,如果同时给出多个模式串,要求找到这一系列模式串在母串存在的匹配个数,我们应该如何处理呢? 基于KMP算 ...
- AC自动机 - 多模式串的匹配运用 --- HDU 2896
病毒侵袭 Problem's Link:http://acm.hdu.edu.cn/showproblem.php?pid=2896 Mean: 略 analyse: AC自动机的运用,多模式串匹配. ...
- AC自动机 - 多模式串的匹配 --- HDU 3695 Computer Virus on Planet Pandora
Problem's Link Mean: 有n个模式串和一篇文章,统计有多少模式串在文章中出现(正反统计两次). analyse: 好久没写AC自动机了,回顾一下AC自动机的知识. 本题在构造文章的时 ...
- AC自动机 - 多模式串的匹配运用 --- HDU 3065
病毒侵袭持续中 Problem's Link:http://acm.hdu.edu.cn/showproblem.php?pid=3065 Mean: 略 analyse: AC自动机的运用. 这一题 ...
- (17/34)AC自动机/后缀数组/后缀自动机(施工中)
快补题别再摸鱼了(17/34) 1.AC自动机 #define maxnode 1000010 #define maxsize 26 struct ahocT{ int ch[maxnode][max ...
- Aho-Corasick automaton(AC自动机)解析及其在算法竞赛中的典型应用举例
摘要: 本文主要讲述了AC自动机的基本思想和实现原理,如何构造AC自动机,着重讲解AC自动机在算法竞赛中的一些典型应用. 什么是AC自动机? 如何构造一个AC自动机? AC自动机在算法竞赛中的典型应用 ...
- HDU - 2222,HDU - 2896,HDU - 3065,ZOJ - 3430 AC自动机求文本串和模式串信息(模板题)
最近正在学AC自动机,按照惯例需要刷一套kuangbin的AC自动机专题巩固 在网上看过很多模板,感觉kuangbin大神的模板最为简洁,于是就选择了用kuangbin大神的模板. AC自动机其实就是 ...
- 字符串(3)AC自动机
AC自动机真神奇,其实说白了就是在trie树上进行kmp模式匹配,不过刚接触确实有些难度,有些思想确实有些难以理解,所以学习的时候最好亲自手动模拟整个算法的全过程,那我就来写篇blog总结一下. 首先 ...
随机推荐
- 推荐一个C#代码混淆器 .NET Reactor【转】
C#的代码辛苦写出来之后,一个反射工具,就可以完全显露出来. 当然,在做项目时,这个功能还不错.因为我就曾在一个项目上使用C#,没有进行任何混淆.结果在项目二年多之后,需要做一些调整,自己保存的源代码 ...
- vim之插入
进入vim插入模式,我们熟知的有: 字符位置插入: i 在光标之前插入 a 在光标之后追加 行位置插入: A 在一行的结尾处追加 I 在一行的开头处插入 o 在光标所在位置的 ...
- 用户管理 之 Linux 系统中的超级权限的控制
在Linux操作系统中,root的权限是最高的,也被称为超级权限的拥有者.普通用户无法执行的操作,root用户都能完成,所以也被称之为超级管理用户. 在系统中,每个文件.目录和进程,都归属于某一个用户 ...
- Java 反射练习
已同步更新至个人blog:http://dxjia.cn/2015/08/java-reflect/ 引用baidubaike上对JAVA反射的说明,如下:JAVA反射机制是在运行状态中,对于任意一个 ...
- Navi.Soft30.开放平台.腾讯.开发手册
1系统简介 1.1功能简述 现在是一个信息时代,并且正在高速发展.以前获取信息的途径非常少,可能只有电视台,收音机等有限的来源,而现在的途径数不胜数,如:QQ,微信,官方网站,个人网站等等 本开发手册 ...
- Firefox终于返回到了Debian stable
6月8日,firefox 45.2以安全修复包的名义回到了Debian oldstable (即wheezy),两天以后,Debian 8 jessie里面也有了(https://packages.d ...
- Winform中进行MD5加密
Winform,c#进行MD5加密直接上步骤: 1.添加引用 2.在.NET选项卡中找到“System.Web” 3.选中之后,点击“确定”即可,此时会在解决方案中看到刚才添加的引用 4.引用名空间: ...
- [转]BloomFilter——大规模数据处理利器
Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法.通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合. 一. 实例 为了说明Bl ...
- phalcon开发工具(phalcon-devtools)
一.简介 Phalcon提供的这个开发工具主要是用来辅助开发,比如生成一些程序的基本框架,生成控制器模型等.使用这个工具我们只需要一个简单的命令即可生成应用的基本框架. 二.下载 github: ht ...
- Mac安装win7
为了给老板的mac air装上win7,研究了大致4-5小时,终于搞定! 新版的air安装实在是太不容易了,现在记录如下: 制作硬件:8G优盘一个(至少大于6G空白优盘一个),Macbook ai ...