【题意】
给出一个n*m的字母矩阵T和一个x*y的字母矩阵S。求S在T中出现了多少次?
【解题方法】
具体见白书218页,前面写的AC 自动机,都是没有加last优化的,但是我写这个题的时候,发现不加last巨难写,好吧我太菜了。所以我选择了这种last优化(好像并没有优化?)的版本,作为AC自动机的第二种板子,方便以后遇到较难的题的处理,这样要方便一些。
将S的每行看做一个串插入ac自动机。用T的每一行去匹配。那么我们可以得到每一次匹配都匹配了S的哪些行的串以及在T的这一行的哪个位置匹配到这个S的串。我们用f[i][j]表示以(i,j)为左上角的T,匹配了S的多少行。设某一次T的第r行匹配了S的第i行,位置是[c,c+y-1],那么令f[r-i][c]++。最后f[i][j]=x的就是能够完整匹配S的位置。
【AC 代码】参考Liurtujia的代码仓库。
//
//UVA 11019 带last 版本
//Created by just_sort 2016/8/20
//All Rights Reserved
//
#include <map>
#include <queue>
#include <cstring>
#include <string>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxnode = 100010;
const int maxs = 26;
void process_match(int pos,int v);
struct acautomata{
int nxt[maxnode][maxs],fail[maxnode],val[maxnode],last[maxnode],sz;
void init(){
sz = 1;
memset(nxt[0],0,sizeof(nxt[0]));
}
int newnode(){
memset(nxt[sz],0,sizeof(nxt[sz]));
val[sz]=0;
return sz++;
}
int idx(int c){return c-'a';}
void insert(char *s,int v){
int u=0,n=strlen(s);
for(int i=0; i<n; i++){
int c=idx(s[i]);
if(!nxt[u][c]){
nxt[u][c]=newnode();
}
u=nxt[u][c];
}
val[u]=v;
}
void report(int pos,int j){//递归打印以节点j结尾的所有字符串
if(j){
process_match(pos,val[j]);
report(pos,last[j]);
}
}
void Find(char *T){
int n=strlen(T),j=0;
for(int i=0; i<n; i++){
int c=idx(T[i]);
j=nxt[j][c];
if(val[j]) report(i,j);
else if(last[j]) report(i,last[j]);
}
}
void build()
{
queue<int>q;fail[0]=0;
for(int i=0; i<maxs; i++){
int u=nxt[0][i];
if(u){fail[u]=0;last[u]=0;q.push(u);}
}
while(q.size()){
int r=q.front();q.pop();
for(int i=0; i<maxs; i++){
int u=nxt[r][i];
if(!u){
nxt[r][i]=nxt[fail[r]][i];
continue;
}
q.push(u);
int v=fail[r];
while(v&&!nxt[v][i]) v=fail[v];
fail[u]=nxt[v][i];
last[u] = val[fail[u]]?fail[u]:last[fail[u]];
}
}
}
}ac;
const int maxn = 1010;
const int maxm = 1010;
const int maxx = 110;
const int maxy = 110;
char text[maxn][maxn],P[maxn][maxn];
int repr[maxn]; //repr[i]为模板第i行的代表元
int nex[maxn]; //nex[i]为模板中与第i行相等的下一行的编号
int len[maxn]; //模板各行的长度
int tr; //当前文本行编号
int cnt[maxn][maxm];//记录答案
void process_match(int pos,int v)// AC自动机每找到一个匹配会调用一次,结束位置为pos,val为v
{
int pr = repr[v-1];//匹配到的模板行编号
int c = pos -len[pr]+1;//匹配到的模板列编号
while(pr>=0){
if(tr>=pr){
cnt[tr-pr][c]++;
}
pr=nex[pr];
}
}
int main()
{
int T,n,m,x,y;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++) scanf("%s",text[i]);
ac.init();
scanf("%d%d",&x,&y);
for(int i=0; i<x; i++){
scanf("%s",P[i]);
len[i] = strlen(P[i]);
repr[i] = i;
nex[i] = -1;
for(int j=0; j<i; j++){
if(strcmp(P[i],P[j])==0){
repr[i] = j;
nex[i] = nex[j];
nex[j] = i;
break;
}
}
if(repr[i]==i) ac.insert(P[i],i+1);
}
ac.build();
memset(cnt,0,sizeof(cnt));
for(tr=0; tr<n; tr++){
ac.Find(text[tr]);
}
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;
}