题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1277

题目:


全文检索

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 2500    Accepted Submission(s): 839

Problem Description

我们大家经常用google检索信息,但是检索信息的程序是很困难编写的;现在请你编写一个简单的全文检索程序。
问题的描述是这样的:给定一个信息流文件,信息完全有数字组成,数字个数不超过60000个,但也不少于60个;再给定一个关键字集合,其中关键字个数不超过10000个,每个关键字的信息数字不超过60个,但也不少于5个;两个不同的关键字的前4个数字是不相同的;由于流文件太长,已经把它分成多行;请你编写一个程序检索出有那些关键字在文件中出现过。

Input

第一行是两个整数M,N;M表示数字信息的行数,N表示关键字的个数;接着是M行信息数字,然后是一个空行;再接着是N行关键字;每个关键字的形式是:[Key No. 1] 84336606737854833158。

Output

输出只有一行,如果检索到有关键字出现,则依次输出,但不能重复,中间有空格,形式如:Found key: [Key No. 9] [Key No. 5];如果没找到,则输出形如:No key can be found !。

Sample Input

20 10
646371829920732613433350295911348731863560763634906583816269
637943246892596447991938395877747771811648872332524287543417
420073458038799863383943942530626367011418831418830378814827
679789991249141417051280978492595526784382732523080941390128
。。。。。。。。。。。。
000361739177065479939154438487026200359760114591903421347697 [Key No. 1] 934134543994403697353070375063
[Key No. 2] 261985859328131064098820791211
[Key No. 3] 306654944587896551585198958148
[Key No. 4] 338705582224622197932744664740
[Key No. 5] 619212279227080486085232196545
[Key No. 6] 333721611669515948347341113196
[Key No. 7] 558413268297940936497001402385
[Key No. 8] 212078302886403292548019629313
[Key No. 9] 877747771811648872332524287543
[Key No. 10] 488616113330539801137218227609

Sample Output

Found key: [Key No. 9] [Key No. 5]

解题思路:


ac自动机法:

在ac自动机模版上稍微做一点修改,存关键字对应的NO编号(初始化编号为0),其他和模版基本上都是一样的。

但是疯狂提交疯狂RE,MLE,TLE,后来看网上的代码发现是match函数写崩了,对于每个遍历到的节点都要去搜索它现在的字符串的后缀是不关键字,是的话存入结果队列中,否则一直找下去。(´・Д・)」

字典树法:

遍历长字符串,每次都取后半截字符串,再在字典树上遍历,如果找到了一个未出现的关键字,存入记录结果的容器中,停止在字典树上的遍历。


感觉我讲的不是太清楚,举个简单的例子吧:

长字符串:

i 0 1 2 3 4 5 6 7 8 9 10 11
s[i] 1 2 3 4 5 6 2 3 4 7 9 2

关键字:No.1 : 2345623        No.2 :23479      No.3: 34562     No.45678

ac自动机匹配过程:

  1. 遍历长字符串,当遍历到i=6的位置时,s[6]=2,从字典树中可以看出虽然最左侧下面的2不是单词节点,但是它指向的fail对应的2是单词节点,将fail的2对应的No编号存入结果数组中
  2. 再接着从原来的2开始在字典树上往下遍历,紧接着遍历到s[7]=3,是单词节点,No编号存入结果数组
  3. 再继续往下遍历s[10]=9,是单词节点,No编号存入结果数组中

所以最终输出的结果是Found key: [Key No. 3] [Key No. 1] [Key No. 2]

字典树匹配过程:

  1. i=0,截取[0,11]的字符串ss,遍历ss,在字典树上并没有找到从s[0]=1开始的单词
  2. i=1,截取[1,11]的字符串ss,遍历ss,在字典上找到了以s[1]=2开头的单词2345623,将该单词对应的No编号存入结果数组,结束遍历
  3. i=2,同理找到了以s[2]=3开头的单词34562,将该单词对应的No编号存入结果数组,结束遍历
  4. 。。。i=6,找到了以s[6]=2开头的单词23479,将该单词对应的No编号存入结果数组,结束遍历

所以最终输出的结果是Found key: [Key No. 1] [Key No. 3] [Key No. 2]

这道题之所以第二种方法也是正确的,因为给出的关键字都不相同,且没有34562,345623这种情况的关键字,否则用第二种方法的话在搜索以3开头的单词时,当搜索到34562的时候就结束遍历了,无法搜索到345623,这样结果就是错误的了。

但是hdu的这道题这两种方法提交都是ac。。。emmm应该是数据太弱了吧。还是以第一种方法为准吧。

还有,虽然输出只有一行,但是要换行,否则PE(;´༎ຶД༎ຶ`)

ac代码:


ac自动机

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
const int maxn=6e4+6;
const int maxt=600005;
using namespace std;
struct node{int next[10];int fail,no;
}state[maxt];
queue<int> q;//存字母的编号
queue<int> res;
int size=1,no=0;
string s,tots="",ss;
void init()
{while(!q.empty()) q.pop();for(int i=0;i<maxt;i++){memset(state[i].next,0,sizeof(state[i].next));state[i].fail=state[i].no=0;}size=1;
}void insert(char *s,int no)
{int len=strlen(s),now=0;for(int i=0;i<len;i++){int id=s[i]-'0';if(!state[now].next[id]){state[now].next[id] = size++;//cout<<state[now].next[id]<<" 对应的数字:"<<id<<endl;}now=state[now].next[id];}state[now].no=no;
}
void build()
{state[0].fail=-1;//根节点q.push(0);while(!q.empty()){int u=q.front();q.pop();for(int i=0;i<10;i++){int now=state[u].next[i];//当前节点的编号if(now)//有这个字母节点{if(u==0) state[now].fail=0;//第一层都指向根节点else{int v=state[u].fail;//父节点的failwhile(v!=-1){if(state[v].next[i]){state[now].fail=state[v].next[i];//指向父节点下面和当前节点字母相同的节点break;}v=state[v].fail;//一直找,父节点fail位置的字母和父节点的字母相同}if(v==-1) state[now].fail=0;//没找到指向根}q.push(now);}}}
}void match(string s)
{int len=s.length(),now=0;for(int i=0;i<len;i++){int id=s[i]-'0';if(state[now].next[id]) now=state[now].next[id];//有当前节点继续往下找else//失配{int p=state[now].fail;while(p!=-1 && state[p].next[id]==0) p=state[p].fail;if(p==-1) now=0;//没找到,回到根,从根开始找s中的下一个字母else now=state[p].next[id];}int u=now;while(u){if(state[u].no){res.push(state[u].no);state[u].no=0;}u=state[u].fail;}}
}
int main()
{//freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);int n,m;char ws[70];init();scanf("%d %d",&m,&n);getline(cin,ss);//读取m,n之后的换行符while(m--){getline(cin,ss);tots+=ss;}getline(cin,ss);while(n--){getline(cin,s);sscanf(s.c_str(),"[Key No. %d] %s",&no,ws);//cout<<no<<" "<<ws<<endl;insert(ws,no);}build();match(tots);if(res.empty()) printf("No key can be found !\n");else{printf("Found key:");while(!res.empty()){int u=res.front();res.pop();printf(" [Key No. %d]",u);}printf("\n");}return 0;
}

字典树:参考博客https://blog.csdn.net/GodJing007/article/details/80993149

#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <ctype.h>
#include <cmath>
#include <stack>
#include <queue>
#include <sstream>
#include <set>
#include <map>
#include <vector>
#include <limits.h>
using namespace std;
typedef long long ll;
const int N = 600000 + 10;
int tree[N][10];
int num[N];
string arr[60100];
string big = "";
int pos = 1, n, m, co = 1;
set<int > s;//记录是否该单词编号是否已经被匹配过,相当于一个vis数组
vector<int> ss;//记录结果
void insert(string a){int root = 0;for(int i = 0; i < a.length(); i++){int n = a[i] - '0';if(!tree[root][n]){tree[root][n] = pos++;}root = tree[root][n];}num[root] = co++;//存编号
}
void find(string a){int root = 0;for(int i = 0; i < a.length(); i++){int n = a[i] - '0';if(!tree[root][n]){return ;}root = tree[root][n];if(num[root]){//是单词节点if(!s.count(num[root])){//s中没有存入该节点ss.push_back(num[root]);s.insert(num[root]);}return ;}}
}
void finds(string a){for(int i = 0; i < a.length(); i++){string b = a.substr(i,a.length());find(b);}
}
int main(){//freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);cin >> n >> m;for(int i = 0; i < n; i++){cin >> arr[i];big += arr[i];}for(int i = 0; i < m; i++){string a, b;cin >> b >> b >> b >> a;insert(a);}finds(big);if(!ss.empty()){cout << "Found key:";for(vector<int>::iterator it = ss.begin(); it != ss.end(); it++){printf(" [Key No. %d]", *it);}cout << endl;}else{cout << "No key can be found !" << endl;}return 0;
}

【HDU1277】全文检索(ac自动机/字典树)相关推荐

  1. 【SAM套路/AC自动机+主席树】CF547E Mike and Friends

    [题目] CF 给定 n n n个串 a i a_i ai​, Q Q Q组询问 a l ∼ a r a_l\sim a_r al​∼ar​中 a x a_{x} ax​出现了多少次. ∑ ∣ a i ...

  2. CodeForces - 1437G Death DBMS(AC自动机fail树上树链剖分建线段树/暴跳fail)

    题目链接:点击查看 题目大意:给出 n 个模式串,每个模式串初始时的权值为 0,然后有 m 次操作: 1 i x:将第 i 个模式串的权值修改为 x 2 s:给出一个字符串 s,询问字符串 s 作为主 ...

  3. 洛谷P5357 - 【模板】AC自动机(二次加强版)(AC自动机+fail树)

    题目链接:点击查看 题目大意:给出n个模式串,问在主串中分别出现了多少次 题目分析:如果像以往那样,在匹配的时候fail指针乱跳的话,那么是错误的AC自动机使用方法,时间复杂度也大大上升,接近于暴力的 ...

  4. BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MB Submit: 2545  Solved: 1419 [Submit][S ...

  5. BZOJ 2754 [SCOI2012]喵星球上的点名 (AC自动机、树状数组)

    吐槽: 为啥很多人用AC自动机暴力跳都过了?复杂度真的对么? 做法一: AC自动机+树状数组 姓名的问题,中间加个特殊字符连起来即可. 肯定是对点名串建AC自动机(map存儿子),然后第一问就相当于问 ...

  6. HDU - 5394 Trie in Tina Town(回文自动机+字典树)

    题目链接:点击查看 题目大意:给出一个字典树,现在需要求出字典树上所有的回文串做出的贡献,为 出现次数*回文串长度,求出这个答案 题目链接:可以直接在字典树上dfs然后维护贡献,不过这就涉及到了回文自 ...

  7. P7456 [CERC2018] The ABCD Murderer (ac自动机+线段树优化dp/反向st)

    做法 ac自动机预处理出最长后缀 f[i]=max(f[i-1],f[i-2],-,f[i-最长后缀])+1; 转移即可 下面有一种反向st维护的学到了 #include <bits/stdc+ ...

  8. 1285. 单词 ac自动机 + fail树

    传送门 文章目录 题意: 思路: 题意: 一篇论文由若干单词构成,且单词间是隔开的,给你nnn个单词,要求你计算每个单词在论文中出现了多少次. 1≤n≤2001\le n\le 2001≤n≤200, ...

  9. BZOJ 2434 Luogu P2414 [NOI2011]阿狸的打字机 (AC自动机、树状数组)

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=2434 题解: 我写的是离线做法,不知道有没有在线做法. 转化一波题意,\(x\)在AC ...

  10. bzoj 2434 [Noi2011]阿狸的打字机(AC自动机+fail树+dfs序+树状数组)

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MB Submit: 3521  Solved: 1913 [Submit][S ...

最新文章

  1. 字节跳动AI Lab社招以及实习生内推
  2. 另辟蹊径,中科院自动化所等首次用图卷积网络解决语义分割难题
  3. FootSwitch脚踏开关:三位USB静音脚踏板脚踏开关脚踏键盘鼠标
  4. 5种JavaScript中常用的排序方法
  5. 笔记-计算机网络基础-TCP/IP vs OSI
  6. 使用idea编写SparkStreaming消费kafka中的数据【小案例】(四)
  7. 54.get set
  8. Go Web 编程--超详细的模板库应用指南
  9. python writelines_Python之write与writelines区别
  10. Page.IsPostBack
  11. 密码学 SM3算法 Python实现
  12. 新巴巴运动网完整教程
  13. C++ QT中国象棋项目讲解(三) 单机双人对战走棋
  14. 知乎 高级操作系统_知乎问题:高级运营和普通运营有哪些区别?(更新版,赞赞赞!)...
  15. Behavior Designer 行为树中文版教程
  16. BSM的两个基本问题与python实现(欧式期权定价公式)
  17. ICMP协议与ping
  18. 集合添加元素python_集 - 百度文库
  19. 世界杯数据清单:真球迷看球必备,伪球迷速成指南(附数据amp;论文)
  20. java吊打面试官系列,java高级程序员面试笔试宝典蔡羽

热门文章

  1. jQuery和asp.net mvc相关资源链接
  2. 8、二叉树的下一个节点(Python)
  3. 信号识别 matlab库,EEG信号MATLAB分析平台设计 ——模式识别部分
  4. js数组再倒数第二个添加元素_js 循环对象数组将元素逐个添加至新数组问题
  5. 三酷猫学python_python学习第6期
  6. 小程序 params_08. 小程序项目实战:设置首页轮播图(3)
  7. php添加开机启动脚本_php-fpm开机自动启动Shell脚本
  8. c语言乘法除法结合律,有关C语言运算符优先级和结合律的思考
  9. php将xml转为array,php将xml数据转化为数组(array)
  10. 利用CSS3实现鼠标悬停在图片上图片缓慢缩放的两种方法