poj1204Word Puzzles,caioj1465地图匹配(AC自动机+搜索)

来源:互联网 发布:网络真人赌博软件开发 编辑:程序博客网 时间:2024/05/29 16:17

题目传送门
题意:
给出有一个L*C的字符地图,地图的行与列都从0开始编号
然后给出一些字符串,求出这些字符串在字符地图上第一次出现的坐标
输出字符串第一个字母的坐标和字符串的方向
字符串的方向是指字符串的走向
A表示正北,B表示东北,C表示正东,D表示东南,E表示正南,F表示西南,G表示正西,H表示西北
且保证字符串的方向是固定的。

好题!绝对的好题!

解法:
每一个字符串去建字典树。
然后用每一个方向上的每一条链去匹配字典树看一下这条链上面有没有这个单词。
有的话比较一下起始点的位置。记录答案即可。
比如:
现在有一个5*5的矩阵。
我们就把每一行从第一列往右去做一次匹配,方向都为正东。
每一列从第一行往下去做匹配,方向都为正南。
每一行从最后一列往左去做匹配,方向都为正西。
每一列最后一行往上去做匹配,方向都为正北。
其他的四个方向也是同理。

每当我问到了单词的结尾。
我如何求开头位置呢?
比如:
现在是正东方向。那么x不变,y每次+1。
记为x=0,y=1
假设现在结尾的位置是edx和edy,长度为len。
如何求开头?

edx-x*(len-1)
edy-y*(len-1)
即为答案。
这里x=0,y=1.
所以x根本没变过,-0
y增加了len-1个,所以-y*(len-1)

差不多就是这样吧。

代码实现:

#include<queue>#include<cstdio>#include<cstring>#include<algorithm>#include<cstdlib>#include<cmath>using namespace std;struct node {    int s,c[31],fail; //fail为AC自动机的失败指针。s在这题中应该是结尾单词的标记。如果该单词问到了结尾就证明该单词出现了!    node() {        s=fail=0;        memset(c,-1,sizeof(c));    }}t[1100000];int id[1100000]; //id表示的是这个单词是第几个输入进来的int tot,n;char a[1100000];void clean(int x) {    t[x].s=t[x].fail=0;    for(int i=1;i<=26;i++)t[x].c[i]=-1;}int eend[1110000]; //eend表示的就是该单词的长度。void bt(int root,int tt) {    int x=root,len=strlen(a+1);    for(int i=1;i<=len;i++) {        int y=a[i]-'A'+1;        if(t[x].c[y]==-1) {            t[x].c[y]=++tot;            clean(tot);        }        x=t[x].c[y];    }    t[x].s++;eend[x]=len;id[x]=tt; //结尾标记。}queue<int> q;void bfs() { //求失败指针,AC自动机模版    int x;    q.push(0);    while(q.empty()==0) {        x=q.front();        for(int i=1;i<=26;i++) {            int son=t[x].c[i];            if(son==-1)                continue;            if(x==0)                 t[son].fail=0;            else {                int j=t[x].fail;                while(j!=0&&t[j].c[i]==-1)                     j=t[j].fail;                t[son].fail=max(t[j].c[i],0);            }            q.push(son);        }        q.pop();    }}char ss[1100][1100];char ans[11]={'A','B','C','D','E','F','G','H'};int dx[11]={-1,-1,0,1,1,1,0,-1}; //八个方向,从正北开始顺时针。int dy[11]={0,1,1,1,0,-1,-1,-1};int m,k,sum[1100000],sx[1100000],sy[1100000];char sss[1100000];bool pd(int x,int y) { //pd是否越界,题目从0开始,我不习惯,我从1开始的,最后答案-1即可。    if(x<1||x>n||y<1||y>m)        return false;    return true;}void dfs(int stx,int sty) { //从(stx,sty)开始递归八个方向。    for(int i=0;i<=7;i++) {        if(pd(stx+dx[i],sty+dy[i])==false)            continue; //如果你往这个方向走的话越界那么就下一个方向        int x=0;        for(int jj=0;jj<=max(n,m);jj++) { //从0开始是因为自己这个起始点也要算上。            int tx=stx+dx[i]*jj,ty=sty+dy[i]*jj; //当前走到哪了            if(pd(tx,ty)==false) //同样越界也要跳出循环                break;            int y=ss[tx][ty]-'A'+1;            while(x!=0&&t[x].c[y]==-1)                 x=t[x].fail; //找到有这个孩子的节点,AC自动机的操作            x=t[x].c[y];            if(x==-1) { //如果还是没有这个孩子就下一个节点                x=0;continue;            }            int j=x;            while(t[j].s>=0)            {  //这就是刚才说的求开头的方法                int st=tx-dx[i]*(eend[j]-1),ed=ty-dy[i]*(eend[j]-1);                if(sum[id[j]]>(st-1)*m+ed) { //sum记录的是起始点的大小                    sum[id[j]]=(st-1)*m+ed; //记录答案                    sx[id[j]]=st;sy[id[j]]=ed;                    sss[id[j]]=ans[i];                 }                t[j].s=-1;                j=t[j].fail; //继续跳            }        }    }}int main(){    scanf("%d%d%d",&n,&m,&k);    for(int i=1;i<=n;i++)        scanf("%s",ss[i]+1);    memset(eend,0,sizeof(eend));    for(int i=1;i<=k;i++) {        scanf("%s",a+1);bt(0,i);    }    bfs();memset(sum,63,sizeof(sum));    for(int i=1;i<=n;i++) { //从边界开始递归才能保证搜到链上面所有的点        dfs(i,1);dfs(i,m);    }    for(int i=1;i<=m;i++) { //同理        dfs(1,i);dfs(n,i);    }    for(int i=1;i<=k;i++) //因为我从的位置从1开始,所以要-1        printf("%d %d %c\n",sx[i]-1,sy[i]-1,sss[i]);    return 0;}

这道题应该是有两种做法的:
一种是将需要匹配的字符串正着建。
一种是反着建。
我用的是正着。
反着建就是将字符串倒过来建。
比如原串为abc,反着建就为cba。
反着建的好处就是我最后一个问到的反而是起始位置,这样的话我就不用去求起始位置了。
正着建就需要求起始位置。

原创粉丝点击