UVA11019----AC自动机(要深刻理解)*

博客详细解析了UVA11019问题,将其转化为二维AC自动机的问题。通过构建模板并对比母串进行匹配,使用cnt数组记录匹配情况。关键在于统计函数的设计,特别是如何处理模板行的重复和移动。博主强调深刻理解算法的重要性。
摘要由CSDN通过智能技术生成

题目地址:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1960

题目意思:

给你一个n*m的字符矩阵

再给你一个x*y的字符矩阵

问你n*m的矩阵里面有多少个x*y的矩阵

我把这道题目理解为一道二维的AC自动机

首先对x*y的里面的每一行都作为一个模板建模

然后再对n*m的矩阵的每一行都作为母串来进行比对

根据大白上面说的我们构造一个cnt[r][c]二维数组,来表示以r,c为左上角的话已经比对模板有多少个了。

统计完了之后,再来查询每个cnt[r][c],如果比对的行数为x,即全部的模板行,那么就是一个,ans++

此题有几个很关键的地方

一个是统计函数,说实话,这题不看大白,我自己估计很难想出来

在统计的时候,拿到了一个匹配模板之后,

首先确定左上角,因为当前行是tr,所以左上角是tr-pr

仔细想一下,当tr变为下一行,而模板的下一行也匹配的话,那不就还是在左上角匹配吗,所以这个是十分巧妙的

再就是next数组,因为小的模板里面可能会有重复的模板行,所以插入的时候我们要只需在AC自动机里面插入一个

但是我们要记录那几个是一样的,所以就可以用next数组来记录,这一点在一次统计的时候,可以起到同时匹配多个模板行的作用。

下面上代码,这个我可是精心注释的哦,只有深刻理解每个算法,才能应付变化多端的题目。

#include<cstdio>
#include<cstring>
#include<queue>
#include<string>
using namespace std;

const int maxnode = 100*100+10;//最多的模板节点个数
const int size = 26;//最多的字符数,a~z

void process_match(int pos,int j);


struct AC{
    int ch[maxnode][size];//每个节点的后继节点
    int f[maxnode];//失配边
    int val[maxnode];//用来表示在模板中哪些是模板的结束点
    int last[maxnode];//输出某个节点相同的末尾节点
    int sz;//这颗trie的节点个数

    void init()//初始化函数
    {
        sz=1;
        memset(ch[0],0,sizeof(ch[0]));
    }

    int idx(char c)//将字符转换为相对应的数字,便于计算
    {
        return c-'a';
    }

    void insert(char *s,int v)//在这颗trie中插入一个模板s,并将它的末尾节点赋val为v
    {
        int u=0;
        int n=strlen(s);
        for(int i=0;i<n;i++)
        {
            int c=idx(s[i]);
            if(!ch[u][c])//如果当前此节点的后继点里面还没有c,那就create一个
            {
                memset(ch[sz],0,sizeof(ch[sz]));
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u=ch[u][c];//沿着树的边继续走,直到把模板的所有点全部加进去
        }
        val[u]=v;//加完了之后,末尾节点就是赋值为v,代表这是某个模板的结尾
    }

    void find(char *T)//在一个字符串中去查找模板,看有没有相陪的
    {
        int n=strlen(T);
        int u=0;
        for(int i=0;i<n;i++)
        {
            int c=idx(T[i]);
            while(u && !ch[u][c])//如果当前节点u的后继节点里面没有c的话,就沿着失配边找,直到找不到或者找到
                u=f[u];
            u=ch[u][c];
            if(val[u])//如果找到的这个u是一个模板的结尾的话就去打印
                report(i,u);
            else if(last[u])//如果这个节点的另一个匹配点存在的话,即不是0,那么也去打印
                report(i,last[u]);//详见report函数
        }
    }

    void getfail()//计算失配边的函数
    {
        queue<int>q;
        f[0]=0;

        for(int c=0;c<size;c++)//初始化模板中,也就是trie中的第一个字母的f和last
        {
            int u=ch[0][c];
            if(u)
            {
                f[u]=0;
                q.push(u);
                last[u]=0;
            }
        }

        while(!q.empty())
        {
            int r=q.front();
            q.pop();
            for(int c=0;c<size;c++)
            {
                int u=ch[r][c];
                if(!u)continue;
                q.push(u);

                //从u的母节点r的失配开始找
                int v=f[r];
                while(v && !ch[v][c])
                    v=f[v];
                f[u]=ch[v][c];
                last[u] = val[f[u]]?f[u]:last[f[u]];
            }
        }
    }

    void report(int pos,int j)//统计以某个模板j且第pos列的
    {
        if(j)
        {
            process_match(pos, val[j]);
            report(pos, last[j]);
        }
    }
};


AC ac;


//这是比较关键的统计函数
const int maxn = 1000 + 10;
const int maxm = 1000 + 10;
const int maxx = 100 + 10;
const int maxy = 100 + 10;
char text[maxn][maxm], P[maxx][maxy];

int repr[maxx]; // repr[i]为模板第i行的“代表元”
int next[maxx]; // next[i]为模板中与第i行相等的下一个行编号
int len[maxx]; // 模板各行的长度
int tr; // 当前文本行编号
int cnt[maxn][maxm];

void process_match(int pos,int u)
{
    int pr=repr[u-1];
    int c=pos-len[pr]+1;
    while(pr >= 0)//一个一个的去加,最后看有木有x个
    {
        if(tr >= pr) // P的行pr出现在在T的tr行,起始列编号为c
            cnt[tr - pr][c]++;
        pr = next[pr];//继续和下一个匹配的计算
    }
}

int main()
{
    int t;
    int ca=1;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            scanf("%s",text[i]);
        int x,y;
        scanf("%d%d",&x,&y);
        ac.init();
        for(int i=0;i<x;i++)
        {
            scanf("%s",P[i]);
            len[i]=strlen(P[i]);
            repr[i]=i;
            next[i]=-1;

            //看前面的模板中有木有与现在的这个相同的
            for(int j=0;j<i;j++)
            {
                //如果有,就进行操作
                if(strcmp(P[i],P[j])==0)
                {
                    repr[i]=j;
                    next[i]=next[j];
                    next[j]=i;//这里表示在第j个模板后面有一个和他相同的即i
                    break;//一定不要忘了这里的break,想一想,为什么
                }
            }
            if(repr[i]==i)//查找过后还是唯一,则插入
            {
                ac.insert(P[i],i+1);
            }
        }//这是进行模板的建立

        ac.getfail();//构造失配边

        memset(cnt,0,sizeof(cnt));
        for(tr=0;tr<n;tr++)
            ac.find(text[tr]);//进行匹配,即对c[r][c]进行更新

        int ans=0;
        for(int i=0;i<n-x+1;i++)
        {
            for(int j=0;j<m-y+1;j++)
            {
                if(cnt[i][j]==x)
                    ans++;
            }
        }

        printf("%d\n",ans);

    }
    return 0;

}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值