题目地址: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;
}