AC自动机练习题1:地图匹配
AC自动机板子,学习之前要是忘记了就看一下
1465: 【AC自动机】地图匹配
时间限制: 1 Sec 内存限制: 256 MB
提交: 78 解决: 46
[提交] [状态] [讨论版] [命题人:admin]题目描述
【题意】给出有一个L*C的字符地图,地图的行与列都从0开始编号
然后给出一些字符串,求出这些字符串在字符地图上第一次出现的坐标
输出字符串第一个字母的坐标和字符串的方向
字符串的方向是指字符串的走向
A表示正北,B表示东北,C表示正东,D表示东南,E表示正南,F表示西南,G表示正西,H表示西北
且保证字符串的方向是固定的
【输入格式】
第一行输入L,C,W(0<L,C,W<=1000)
L表示行数,C表示列数,W表示字符串的数量
然后输入L*C的字符矩阵
最后输入W行字符串
【输出格式】
输出W行,每行对应第i个字符串第一个字母的坐标和字符串的方向
【样例输入】
20 20 10
QWSPILAATIRAGRAMYKEI
AGTRCLQAXLPOIJLFVBUQ
TQTKAZXVMRWALEMAPKCW
LIEACNKAZXKPOTPIZCEO
FGKLSTCBTROPICALBLBC
JEWHJEEWSMLPOEKORORA
LUPQWRNJOAAGJKMUSJAE
KRQEIOLOAOQPRTVILCBZ
QOPUCAJSPPOUTMTSLPSF
LPOUYTRFGMMLKIUISXSW
WAHCPOIYTGAKLMNAHBVA
EIAKHPLBGSMCLOGNGJML
LDTIKENVCSWQAZUAOEAL
HOPLPGEJKMNUTIIORMNC
LOIUFTGSQACAXMOPBEIO
QOASDHOPEPNBUYUYOBXB
IONIAELOJHSWASMOUTRK
HPOIYTJPLNAQWDRIBITG
LPOINUYMRTEMPTMLMNBO
PAFCOPLHAVAIANALBPFS
MARGARITA
ALEMA
BARBECUE
TROPICAL
SUPREMA
LOUISIANA
CHEESEHAM
EUROPA
HAVAIANA
CAMPONESA
【样例输出】
0 15 G
2 11 C
7 18 A
4 8 C
16 13 B
4 15 E
10 3 D
5 1 E
19 7 C
11 11 H
首先这个几乎和AC自动机的板子是一样的,只是这个变成了二维的,然后我们现在来看一下这道题要注意的一些小细节
- 首先我们这道题因为一个矩阵当中出现的字母是有很多次的,但是我们要找到最早出现的满足条件的字母,所以我们知道要多增加一个变量来保存我们当前的这个字母有没有被搜索过,然后因为方向的增加,这就意味着我们应该要将每个位置边界位置预处理
- 然后就是说我们的建树要倒着建,为什么呢?因为这样子的话,我们就可以直接在搜索结尾的时候,找到开头的,然后记录下来位置
主要就是这两个细节,剩下的看几乎和AC自动机板子一样的代码
代码的实现
(注释版,我觉得不看注释版都可以理解的代码就尽量不看吧
/*有一个要注意的,就是横纵左边的起始位置为0,所以我们在计算字符串的时候一定要是开头为0*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
const int dx[]={-,-,,,, , ,-};
const int dy[]={ , ,,,,-,-,-};
/*正西 西南 正南 东南 正东 东北 正北 西北*/
struct trie/*字典树*/
{
int s,cnt[],fail,b;
/*fail就是失败指针,cnt就是孩子,s记录开头所对应的数据,b是记录他有没有被记录过,这里就没有必要初始化了,一百万初始化还是不太好*/
}tr[];
struct node/*保存答案的结构体*/
{
int x,y,c;/*x,y表示左边,c表示方向*/
}a[];
char map[][],s[];
int tot,list[],n,m,t;
void clean(int x)/*虽然没有多组数据,但是也要清空,因为我们在建树的时候清空子树可以节省时间*/
{
tr[x].b=tr[x].s=tr[x].fail=;
memset(tr[x].cnt,-,sizeof(tr[x].cnt));
}
void build_tree(int id)/*建树的板子*/
{
int x=; int len=strlen(s);
for(int i=len-;i>=;i--)/*倒着建树,方便匹配时记录匹配的终点即为原串的起始点*/
{
int y=s[i]-'A';/*没有+1是因为我们从0开始*/
if(tr[x].cnt[y]==-)
{
tr[x].cnt[y]=++tot;
clean(tot);
}
x=tr[x].cnt[y];
}
tr[x].s=id;/*记录这个单词开头的编号,因为我们是倒着建树的
也就是说我们的tot对应的单词是开头的,把他为第几组数据记录下来*/
}
void bfs()/*构造失败指针的板子*/
{
list[]=; int head=,tail=;
while(head<=tail)
{
int x=list[head];
for(int i=;i<;i++)
{
int son=tr[x].cnt[i];
if(son==-) continue;
if(x==) tr[son].fail=;
else
{
int j=tr[x].fail;
while(j!= && tr[j].cnt[i]==-) j=tr[j].fail;
tr[son].fail=max(tr[j].cnt[i],);
}
list[++tail]=son;
}
head++;
}
}
bool check(int x,int y)/*判断一下这个左边能不能进入搜索的范围*/
{
if(x< || y< || x>=n || y>=m) return ;
return ;
}
void query(int x,int y,int c)
{
int w=;
while(check(x,y))/*可以进入搜索范围*/
{
int j=map[x][y]-'A';/*把他转化为个数*/
while(w!= && tr[w].cnt[j]==-) w=tr[w].fail;
if(tr[w].cnt[j]!=-) w=tr[w].cnt[j];/*同样的就是找失败指针来记录*/
for(int k=w; k!= && !tr[k].b;k=tr[k].fail)
/*从我们当前当的位置开始,保证他不是根节点,并且没有记录过,然后找他的失败指针*/
{
if(tr[k].s)/*找到原串被匹配的起始点*/
{
int l=tr[k].s;
a[l].x=x;
a[l].y=y;/*记录下来横纵坐标*/
a[l].c=(c+)%;/*同种类型的有四种,全部类型有八种*/
}
tr[k].b=;/*更新为记录过*/
}
x+=dx[c],y+=dy[c];/*按照这个方向往下寻找*/
}
}
int main()
{
scanf("%d%d%d",&n,&m,&t);
clean(); tot=;
memset(a,-,sizeof(a));/*初始化*/
for(int i=;i<n;i++) scanf("%s",map[i]);
/*直接把一行字符输进去,一维的我们是直接map+1,和现在一样*/
for(int i=;i<=t;i++)
{
scanf("%s",s);
build_tree(i);/*建树*/
}
bfs();/*构造失败指针*/
/*总共有八个方向,0~7,从最上面开始按照顺时针一周*/
for(int i=;i<n;i++)
{
query(i,,); query(i,,); query(i,,);/*右边*/
query(i,m-,); query(i,m-,); query(i,m-,);/*左边*/
}
for(int i=;i<m;i++)
{
query(,i,); query(,i,); query(,i,);/*下面*/
query(n-,i,); query(n-,i,); query(n-,i,);/*上面*/
}
/*两重循环加一起起始就是把我们矩阵的外围给围起来的,起始就是说把最外面的有可能走的方向都用AC自动机找一遍*/
for(int i=;i<=t;i++) printf("%d %d %c\n",a[i].x,a[i].y,a[i].c+'A');
return ;
}
Tristan Code 注释版
(非注释版,一个更好锻炼理解代码能力的代码)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
const int dx[]={-,-,,,, , ,-};
const int dy[]={ , ,,,,-,-,-};
struct trie
{
int s,cnt[],fail,b;
}tr[];
struct node
{
int x,y,c;
}a[];
char map[][],s[];
int tot,list[],n,m,t;
void clean(int x)
{
tr[x].b=tr[x].s=tr[x].fail=;
memset(tr[x].cnt,-,sizeof(tr[x].cnt));
}
void build_tree(int id)
{
int x=; int len=strlen(s);
for(int i=len-;i>=;i--)
{
int y=s[i]-'A';
if(tr[x].cnt[y]==-)
{
tr[x].cnt[y]=++tot;
clean(tot);
}
x=tr[x].cnt[y];
}
tr[x].s=id;
}
void bfs()
{
list[]=; int head=,tail=;
while(head<=tail)
{
int x=list[head];
for(int i=;i<;i++)
{
int son=tr[x].cnt[i];
if(son==-) continue;
if(x==) tr[son].fail=;
else
{
int j=tr[x].fail;
while(j!= && tr[j].cnt[i]==-) j=tr[j].fail;
tr[son].fail=max(tr[j].cnt[i],);
}
list[++tail]=son;
}
head++;
}
}
bool check(int x,int y)
{
if(x< || y< || x>=n || y>=m) return ;
return ;
}
void query(int x,int y,int c)
{
int w=;
while(check(x,y))
{
int j=map[x][y]-'A';
while(w!= && tr[w].cnt[j]==-) w=tr[w].fail;
if(tr[w].cnt[j]!=-) w=tr[w].cnt[j];
for(int k=w; k!= && !tr[k].b ;k=tr[k].fail)
{
if(tr[k].s)
{
int l=tr[k].s;
a[l].x=x;
a[l].y=y;
a[l].c=(c+)%;
}
tr[k].b=;
}
x+=dx[c],y+=dy[c];
}
}
int main()
{
scanf("%d%d%d",&n,&m,&t);
clean(); tot=;
memset(a,-,sizeof(a));
for(int i=;i<n;i++) scanf("%s",map[i]);
for(int i=;i<=t;i++)
{
scanf("%s",s);
build_tree(i);
}
bfs();
for(int i=;i<n;i++)
{
query(i,,); query(i,,); query(i,,);
query(i,m-,); query(i,m-,); query(i,m-,);
}
for(int i=;i<m;i++)
{
query(,i,); query(,i,); query(,i,);
query(n-,i,); query(n-,i,); query(n-,i,);
}
for(int i=;i<=t;i++) printf("%d %d %c\n",a[i].x,a[i].y,a[i].c+'A');
return ;
}
Tristan Code 非注释版
AC自动机练习题1:地图匹配的更多相关文章
- AC自动机——多个kmp匹配
		(并不能自动AC) 介绍: Aho-Corasick automaton,最经典的处理多个模式串的匹配问题. 是kmp和字典树的结合. 精髓与灵魂: ①利用trie处理多个模式串 ②引入fail指针. ... 
- ac自动机暴力跳fail匹配——hdu5880
		很简单的题,ac自动机里再维护一个len表示每个状态的串长,用s去query时每到一个结点都要暴力跳fail,因为有可能这个结点不是,但是其fail是危险结点,找到一个就直接break 再用个差分数组 ... 
- Codeforces 590E - Birthday(AC 自动机+Dilworth 定理+二分图匹配)
		题面传送门 AC 自动机有时只是辅助建图的工具,真的 首先看到多串问题,果断建出 AC 自动机.设 \(m=\sum|s_i|\). 不难发现子串的包含关系构成了一个偏序集,于是我们考虑转化为图论,若 ... 
- 使用AC自动机解决文章匹配多个候选词问题
		解决的问题 KMP算法用于单个字符串匹配,AC自动机用于文章中匹配多个候选词. 流程 第一步,先将候选词先建立前缀树. 第二步,以宽度优先遍历的方式把前缀树的每个节点设置fail指针, 头节点的fai ... 
- bzoj2938(ac自动机)
		刚学了ac自动机,去hzwer上找了道练习题: 串是安全的就说明ac自动机不会找到匹配,考虑ac自动机的匹配过程: 我们把val等于1的点删掉和fail指针指向被删掉的点删掉: 如果剩下的图有环,就有 ... 
- HDU-4518 吉哥系列故事——最终数 AC自动机+数位DP
		题意:如果一个数中的某一段是长度大于2的菲波那契数,那么这个数就被定义为F数,前几个F数是13,21,34,55......将这些数字进行编号,a1 = 13, a2 = 21.现给定一个数n,输出和 ... 
- UVa 11468 (AC自动机 概率DP) Substring
		将K个模板串构成一个AC自动机,那些能匹配到的单词节点都称之为禁止节点. 然后问题就变成了在Tire树上走L步且不经过禁止节点的概率. 根据全概率公式用记忆化搜索求解. #include <cs ... 
- 【暑假】[实用数据结构] AC自动机
		Aho-Corasick自动机 算法: <功能> AC自动机用于解决文本一个而模板有多个的问题. AC自动机可以成功将多模板匹配,匹配意味着算法可以找到每一个模板在文本中出现的位置. & ... 
- [转] AC自动机详解
		转载自:http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d AC自动机详解 AC自动机是用来处理多串匹配问题的,即给你很多串,再给你一篇文 ... 
随机推荐
- js获取键盘编码
			原理:键盘上的按键都有各自的键码,通过这个键码可以来判断按下的是哪个键,下面函数可以获取键盘的键码,按下键盘按键就会在控制台打印出相应的键码 document.addEventListener(&qu ... 
- Centos 安装字体库 以及解决confluence 旧文档数据的乱码
			首先,第一步我们需要执行以下的命令来安装字体管理工具: yum install -y fontconfig mkfontscale 然后我们到(Windows系统)“c:/windows/fonts ... 
- 使用vlc 或 ffmpeg发布RTP/UDP视频服务
			一.FFmpeg 测试环境Centos 发布端: ffmpeg -re -stream_loop -1 -i test.ts -vcodec copy -acodec copy -f rtp_mpeg ... 
- 09.变态跳台阶 Java
			题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 思路 0:0 1:(1) 2:(1,1)(2) 3:(1,1,1)(2,1)( ... 
- TCP层shutdown系统调用的实现分析
			概述 shutdown系统调用在tcp层会调用两个函数,对于ESTABLISHED状态需要调用tcp_shutdown关闭连接,对于LISTEN和SYN_SENT状态则需要以非阻塞模式调用tcp_di ... 
- TCP路径MTU发现
			路径MTU 当在同一个网络上的两台主机互相通信时,该网络的MTU是非常重要的.当时如果两台主机之间的通信要通过多个网络,那么每个网络的链路层就可能有不同的MTU.重要的不是两台主机所在网络的MTU,而 ... 
- C++ 学习时的错误记录
			1. 关于C++相关的文件扩展名 c++程序中的头文件扩展名包括: .h .hpp .hxx C++程序中源文件的扩展名包括: .cc .cpp .cxx 2.C++程序编译过程 3. 处理错误 4. ... 
- python全栈开发第6天
			作业一:1) 开启Linux系统前添加一块大小为15G的SCSI硬盘 2) 开启系统,右击桌面,打开终端 3) 为新加的硬盘分区,一个主分区大小为5G,剩余空间给扩展分区,在扩展分区上划分1个逻辑分区 ... 
- jenkins介绍及其简单操作
			一.jenkins简介 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. Jenkins功能包 ... 
- 谷歌云服务器XShell登录
			一,谷歌云服务器,默认用浏览器进行SSH链接,而且也不告知密码.以Centos为例,先使用浏览器连接 1,给root修改密码 sudo passwd root 2,编辑ssh配置文件 sudo nan ... 
 
			
		
