//AC自动机解决一类文本串匹配多关键字的问题
//fail就是Trie上的next,当失配时直接跳转到下一个节点,继续匹配
//可以用Fail的递归来传递敏感串的状态,
//insert_()前要init(),query_()之前要build().
//要充分利用end_数组来存储状态
//AC自动机其实就是长度为k的字符串转移到长度为k+1的串的转移状态
//若当前共有cnt个节点,则转移共有cnt*MAX个,MAX表示下一个字符可能的个数
//对于未出现过的文本串,直接转移回root,否则转移到下一位
//fail[i] 表示作为节点为i的串的后缀中在Trie上出现过的前缀
const int maxn = 5e5+5;//64MB可以开到2e6*30,尽量开大
struct ACTrie
{
int tree[maxn][26],fail[maxn],end_[maxn];//end_数组一般表示是否为结束字符
int root,cnt;
int newnode()
{
for(int i=0;i<26;i++)
tree[cnt][i]=-1;
end_[cnt++]=0;
return cnt-1;
}
void init()
{
cnt=0;
root=newnode();
}
void insert_(char str[])
{
int len= strlen(str);
int pos=root;
for(int i=0;iint id=str[i]-'a';
if(tree[pos][id]==-1)
tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]++;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[now][i]==-1)
tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
}
}
}
int query(char str[])
{
int len=strlen(str);
int now=root;
int res=0;
for(int i=0;i'a'];
int temp=now;
while(temp!=root)//类似kmp,不断递归寻找作为后缀出现过的前缀,计算贡献
{
res+=end_[temp];
end_[temp]=0;//当每个单词只统计一次而且只有一个查询,重置为0,
temp=fail[temp];
}
}
return res;
}
};
HDU2222
题意就是统计一个文本串中出现过多少个给定的字符串,每个给定的字符串只统计一次。
这就是AC自动机的裸题,我们直到AC自动机的精髓在于状态转移,我们可以从k状态转移到k+1的状态,所以我们只需要把文本串放到AC自动机上面沿着Trie树跑,然后用Fail来递归寻找出现过的前缀,统计贡献就好了,注意统计贡献的时候要将贡献清空,否则一个字符串将统计多次
HDU2222代码
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
struct ACTrie
{
int tree[maxn][26],fail[maxn],end_[maxn];
int root,cnt;
int newnode()
{
for(int i=0;i<26;i++)
tree[cnt][i]=-1;
end_[cnt++]=0;
return cnt-1;
}
void init()
{
cnt=0;
root=newnode();
}
void insert_(char str[])
{
int len= strlen(str);
int pos=root;
for(int i=0;iint id=str[i]-'a';
if(tree[pos][id]==-1)
tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]++;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[now][i]==-1)
tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
}
}
}
int query(char str[])
{
int len=strlen(str);
int now=root;
int res=0;
for(int i=0;i'a'];//在Trie上不断向后跑
int temp=now;
while(temp!=root)
{
res+=end_[temp];
end_[temp]=0;
temp=fail[temp];
}
}
return res;
}
};
char str[maxn*2];
ACTrie ac;
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
ac.init();
for(int i=0;iscanf("%s",str);
ac.insert_(str);
}
ac.build();
scanf("%s",str);
printf("%d\n",ac.query(str));
}
return 0;
}
HDU2896
题意就是给你一些病毒网站的关键字,再给你一些网站链接,问每个网站出现过哪些病毒网站关键字
我们只需要用病毒网站关键字构建出AC自动机,之后对每个网站开一个set存储与见过的病毒网站关键字下标就可以了,注意本题字符为ASCII码可见字符,所以tree[][]数组第二维要开到128
HDU2896代码
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
struct ACTrie
{
int tree[maxn][128],fail[maxn],end_[maxn];
int root,cnt,num;
set<int> s;
int newnode()
{
for(int i=0;i<128;i++)
tree[cnt][i]=-1;
end_[cnt]=0;
return cnt++;
}
void init()
{
cnt=0;
num=0;
root=newnode();
}
void insert_(char str[])
{
int len=strlen(str);
int pos=root;
for(int i=0;iint id=str[i];
if(tree[pos][id]==-1) tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]=++num;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<128;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<128;i++)
{
if(tree[now][i]==-1)
tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
}
}
}
void query(char str[])
{
s.clear();
int len=strlen(str);
int now=root;
int res=0;
for(int i=0;iint temp=now;
while(temp!=root)//对每一个前缀串递归统计是否有后缀是病毒串
{
if(end_[temp]!=0) s.insert(end_[temp]);
temp=fail[temp];
}
}
return ;
}
};
ACTrie ac;
char str[10005];
int main()
{
int n,m;
int flag=0;
scanf("%d",&n);
ac.init();
while(n--)
{
scanf("%s",str);
ac.insert_(str);
}
ac.build();
scanf("%d",&m);
int cnt=0;
int ans=0;
while(m--)
{
scanf("%s",str);
ac.query(str);
cnt++;
if(ac.s.size()!=0)
{
ans++;
set<int>::iterator it;
printf("web %d:",cnt);
for(it=ac.s.begin();it!=ac.s.end();++it)
{
printf(" %d",*it);
}
printf("\n");
}
}
printf("total: %d\n",ans);
return 0;
}
HDU3065
题意就是给你一些病毒网站关键字,统计每个关键字的出现次数,可重叠。
我们用病毒网站关键字搭建好AC自动机,用文本串在上面跑,由于每个Fail递归的过程是必包含当前后缀的(仔细思考),所以是不会出现重复统计的情况的,所以我们只需要在递归的时候对每个关键字计数,最后输出就可以了。
注意:多组输入,ASCII码可见字符有128种
HDU3065代码
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
struct ACTrie
{
int tree[maxn][128],fail[maxn],end_[maxn],sum[maxn];//用end_数组存储该单词的下标,sum统计出现次数
int root,cnt,num;
int newnode()
{
for(int i=0;i<128;i++)
tree[cnt][i]=-1;
end_[cnt]=0;
return cnt++;
}
void init()
{
cnt=0;
num=0;
root=newnode();
return ;
}
void insert_(char str[])
{
int len=strlen(str);
int pos=root;
for(int i=0;iint id=str[i];
if(tree[pos][id]==-1) tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]=++num;//用时间戳记录下标
sum[num]=0;
return ;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<128;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<128;i++)
{
if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
}
}
return ;
}
void query(char str[])
{
int len=strlen(str);
int now=root;
for(int i=0;iint tmp=now;
while(tmp!=root)
{
if(end_[tmp]) sum[end_[tmp]]++;//统计答案
tmp=fail[tmp];
}
}
return ;
}
};
char str[1005][55];
ACTrie ac;
char str0[2000005];
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
ac.init();
for(int i=1;i<=n;i++)
{
scanf("%s",str[i]);
ac.insert_(str[i]);
}
ac.build();
scanf("%s",str0);
ac.query(str0);
for(int i=1;i<=ac.num;i++)
{
if(ac.sum[i]!=0)
printf("%s: %d\n",str[i],ac.sum[i]);
}
}
return 0;
}
ZOJ3430
题意就是给你一个加密的单词,加密方式就是先把字符按照ASCII表转为2进制,再截取6个为一段转为10进制,再去表中改为对应字符。
我们只需要把所有关键字和文本串都解码之后,就是一个最朴素的AC自动机了。
密文转换方法是向kuangbin大神学习的,我们通过思考可以发现,解码的过程就是把四个字符转换为3个字符的过程,所以我们只要对加密串进行一些二进制操作,就可以直接将24位二进制码分为3个八位二进制码。具体过程请参考代码。
注意此题字符可能有256种,所以要用unsigned char
而且 ZOJ RE和MLE均返回 Segmentation Fault ,此题卡内存比较严重,需要好好计算一下
ZOJ3430代码
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn =5e4+5;
unsigned char aa[maxn];
#define dbg(x) cout<<#x<<" "<
struct ACTrie
{
int tree[maxn][256], fail[maxn],end_[maxn];
int root,cnt,num;
set<int> s;
int newnode()
{
for(int i=0;i<256;i++)
tree[cnt][i]=-1;
end_[cnt]=0;
return cnt++;
}
void init()
{
cnt=0;
num=0;
root=newnode();
}
void insert_(unsigned char str[],int len)
{
int pos=root;
for(int i=0;iint id=str[i];
if(tree[pos][id]==-1) tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]=++num;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<256;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<256;i++)
{
if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
}
}
}
int query(unsigned char *str,int len)
{
s.clear();
int now=root;
int res=0;
for(int i=0;iint id=(int)str[i];
now=tree[now][id];
int temp=now;
while(temp!=root)
{
if(end_[temp]!=0) s.insert(end_[temp]);
temp=fail[temp];
}
}
return (int)s.size();
}
};
unsigned char cal(char c)
{
if(c>='A'&&c<='Z')
{
return c-'A';
}
if(c>='a'&&c<='z')
{
return c-'a'+26;
}
if(c>='0'&&c<='9')
{
return c-'0'+52;
}
if(c=='+') return 62;
else return 63;
}
int change(unsigned char str[],int len)
{
int t=0;
for(int i=0;i4)//每四个字符计算一次,转换为三个字符
{
aa[t++]=((str[i]<<2)|(str[i+1]>>4));//截取第一个字符的全部+第二个字符前两位
if(i+2 < len)
aa[t++]=( (str[i+1]<<4)|(str[i+2]>>2) );//截取第二个字符后四位+第三个字符前四位
if(i+3 < len)
aa[t++]= ( (str[i+2]<<6)|str[i+3] );//截取第三个字符后两位+第四个字符全部
}
return t;
}
ACTrie ac;
char str[maxn];
unsigned char ss[maxn];
int main()
{
int n,m;
while(scanf("%d",&n)!=EOF)
{
ac.init();
while(n--)
{
scanf("%s",str);
int len=strlen(str);
while(str[len-1]=='=') len--;
for(int i=0;iint len2 = change(ss,len);
ac.insert_(aa,len2);//转换之后插入AC自动机
}
ac.build();
scanf("%d",&m);
while(m--)
{
//gets(str);
scanf("%s",str);
int len=strlen(str);
while(str[len-1]=='=') len--;
for(int i=0;iint len2 = change(ss,len);
printf("%d\n",ac.query(aa,len2));
}
printf("\n");
}
return 0;
}
POJ2778
本题题意是给你一个字符集和一个长度m,还有一些敏感串,求出字符集构造出的长度为m的字符串中不包含敏感串的串的个数。
我们用到AC自动机的性质,想象一下如果从len=k向len=k+1转移,AC自动机上每个状态之间有多少种转移方法,就可以构造出对应的转移矩阵,再利用矩阵快速幂就可以求解。在构造转移矩阵的时候,要注意如果某串的后缀是敏感串,也是不可以转移的。
转移矩阵 Tn T n T[i][j] T [ i ] [ j ] 即表示i状态转移n次到达j状态的方案数,所以最后统计第一行的和,也就是所有初始状态转移n次得到的状态之和了。
你可能会想到一些原本不在AC自动机上面的状态是不是没有被算,其实所有不在AC自动机上的状态都是不被约束的状态,他们是和从root开始的状态相同的,我们在构建fail的时候就把这些串下标都存为root,所以直接算转移矩阵的时候直接转移就好了。(慢慢理解,慢慢理解
POJ2778代码
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define dbg(x) cout<<#x<<" "<
typedef unsigned long long ll;
const int maxn = 2e6+5;
const int Mod=100000;
map<char,int> mp;
struct ACTrie
{
int tree[maxn][4],fail[maxn];
int end_[maxn];
int root,num,cnt;
int newnode()
{
for(int i=0;i<4;i++)
tree[cnt][i]=-1;
end_[cnt]=0;
return cnt++;
}
void init()
{
cnt=0;
num=0;
root=newnode();
}
void insert_(char str[])
{
int pos=root;
int len=strlen(str);
for(int i=0;iint id=mp[str[i]];
if(tree[pos][id]==-1) tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]=1;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<4;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<4;i++)
{
if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
end_[tree[now][i]]|=end_[tree[fail[now]][i]];//注意这个过程,若该字符串的后缀为病毒串,则该字符串也是病毒串。
}
}
}
};
ACTrie ac;
struct mat
{
ll jz[110][110];
};
mat make_mat()
{
mat res;
memset(res.jz,0,sizeof(res.jz));
for(int i=0;iif(ac.end_[i]) continue;//转移之前为病毒串不统计
for(int j=0;j<4;j++)
{
if(ac.end_[ac.tree[i][j]]) continue;//转移之后为病毒串不统计
++res.jz[i][ac.tree[i][j]];
}
}
return res;
}
mat mat_mul(mat x,mat y)
{
mat res;
memset(res.jz,0,sizeof(res.jz));
for(int i=0;ifor(int j=0;jfor(int k=0;kreturn res;
}
ll power_mod (ll b)//.res是系数矩阵,ans是变换矩阵左->ans,右->res.
{
mat ans,res;
res=make_mat();
memset(ans.jz,0,sizeof(ans.jz));
for(int i=0;i1;
while(b>0)
{
if(b&1) ans=mat_mul(res,ans);//所以应该系数矩阵在前ans,res);
b=b>>1;
res=mat_mul(res,res);
}
ll tmp=0;
for(int i=0;i0][i]%Mod)%Mod;
return tmp;//返回指定位置元素
}
char str[11];
int main()
{
mp['A']=0;
mp['C']=1;
mp['T']=2;
mp['G']=3;
int n;
ll m;
while(scanf("%d%I64d",&n,&m)!=EOF)
{
ac.init();
while(n--)
{
scanf("%s",str);
ac.insert_(str);
}
ac.build();
printf("%I64d\n",power_mod(m));
}
return 0;
}
HDU2243
在做本题之前推荐做POJ2778
POJ2778求的是用给定字符集构造出的长度为n的字符串中没出现过给定字符串的字符串有多少个
本题统计的是出现过的,那么我们只需要算出一共可能的种数,再算出长度为1-n可能的出现过给定字符串的字符串个数,相减就是答案。
首先,我们先计算一共可能的种数,
设 f[i]=261+262+.....26i f [ i ] = 26 1 + 26 2 + . . . . .26 i
所以 f[i]=26∗f[i−1]+26 f [ i ] = 26 ∗ f [ i − 1 ] + 26
所以我们可以用矩阵快速幂求出一共可能的种数sum,注意这里要用unsigned long long
之后我们利用上题的做法求出转移矩阵,设转移矩阵为 T T ,那么 Tn T n 的第一行的和即为长度为n的串的转移方案而如果我们想求出所有长度的方案数之和,我们就需要在转移矩阵上加一维,用来存储上一个状态第一行之和,这样最后 Tn T n 的第一行之和就是长度为1-n所有方案之和sum2(慢慢理解,想一下矩阵快速幂
最后用sum-sum2即是最终结果,注意所有变量都应该用unsigned long long。而且这里不能直接输出sum-sum2会出现负数,应该用sum-=sum2才能实现自动溢出。
HDU2243代码
//由于本题存在两个大小不同的矩阵,所以开了两个struct和两个分别矩阵快速幂函数
//但是如果用类去构造应该能清晰一些。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define dbg(x) cout<<#x<<" "<
typedef unsigned long long ll;
const int maxn = 2e6+5;
map<char,int> mp;
struct ACTrie
{
int tree[maxn][27],fail[maxn];
int end_[maxn];
int root,num,cnt;
int newnode()
{
for(int i=0;i<26;i++)
tree[cnt][i]=-1;
end_[cnt]=0;
return cnt++;
}
void init()
{
cnt=0;
num=0;
root=newnode();
}
void insert_(char str[])
{
int pos=root;
int len=strlen(str);
for(int i=0;iint id=str[i]-'a';
if(tree[pos][id]==-1) tree[pos][id]=newnode();
pos=tree[pos][id];
}
end_[pos]=1;
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
end_[tree[now][i]]|=end_[tree[fail[now]][i]];
}
}
}
};
ACTrie ac;
struct mat
{
ll jz[110][110];
};
mat make_mat()
{
mat res;
memset(res.jz,0,sizeof(res.jz));
for(int i=0;iif(ac.end_[i]) continue;
for(int j=0;j<26;j++)
{
if(ac.end_[ac.tree[i][j]]) continue;
++res.jz[i][ac.tree[i][j]];
}
}
for(int i=0;i<=ac.cnt;i++)//在最后一列加上1,实现当前行和的转移
{
res.jz[i][ac.cnt]=1;
}
return res;
}
mat mat_mul(mat x,mat y)
{
mat res;
memset(res.jz,0,sizeof(res.jz));
for(int i=0;i<=ac.cnt;i++)
for(int j=0;j<=ac.cnt;j++)
for(int k=0;k<=ac.cnt;k++)
res.jz[i][j]=res.jz[i][j]+x.jz[i][k]*y.jz[k][j];
return res;
}
ll power_mod (ll b)//.res是系数矩阵,ans是变换矩阵左->ans,右->res.
{
mat ans,res;
res=make_mat();
memset(ans.jz,0,sizeof(ans.jz));
for(int i=0;i<=ac.cnt;i++)
ans.jz[i][i]=1;
while(b>0)
{
if(b&1) ans=mat_mul(res,ans);//所以应该系数矩阵在前ans,res);
b=b>>1;
res=mat_mul(res,res);
}
ll tmp=0;
for(int i=0;i<=ac.cnt;i++)
tmp=tmp+ans.jz[0][i];
return tmp;//返回指定位置元素
}
char str[11];
struct mat2
{
ll jz[2][2];
};
mat2 mat_mul2(mat2 x,mat2 y)
{
mat2 res;
memset(res.jz,0,sizeof(res.jz));
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)
res.jz[i][j]=res.jz[i][j]+x.jz[i][k]*y.jz[k][j];
return res;
}
ll power_mod2(ll b)//.res是系数矩阵,ans是变换矩阵左->ans,右->res.
{
mat2 ans,res;
res.jz[0][0]=26;
res.jz[0][1]=1;
res.jz[1][0]=0;
res.jz[1][1]=1;//根据要求设定系数单位矩阵,
ans.jz[0][0]=0;
ans.jz[1][0]=26;//初始化ans矩阵,只需要初始化每行的第一列
while(b>0)
{
if(b&1) ans=mat_mul2(res,ans);//所以应该系数矩阵在前ans,res);
b=b>>1;
res=mat_mul2(res,res);
//cout<
}
return ans.jz[0][0];//返回指定位置元素
}
int main()
{
int n;
ll m;
while(scanf("%d%I64d",&n,&m)!=EOF)
{
ac.init();
while(n--)
{
scanf("%s",str);
ac.insert_(str);
}
ac.build();
ll tmp=power_mod2(m)+1;//加上长度为0的串
ll res=power_mod(m);
tmp-=res;//注意是-=不是直接输出
cout<return 0;
}
UVA11019
本体题意就是给你AB两个字符矩阵,问你B矩阵在A矩阵中的出现次数
我们用B矩阵的每一行插入Trie树,用A的每一行去跑AC自动机,得到A的每个位置可以匹配B的哪些行,存到一个三维矩阵con[i][j][k]里,con[i][j][k]表示A矩阵第i行第j列的点可匹配B矩阵中的第k行,之后枚举A矩阵起点,然后向下扫x行,检验是否都可以匹配,都可以匹配就更新答案。
注意B中可能存在某些相同行,所以用一个vector进行存储
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
int strl[1005];
int n,m,x,y;
bool con[1005][1005][105];
struct ACTrie
{
int tree[maxn][26],fail[maxn],end_[maxn];
int root,cnt;
vector<int> v[maxn];
int newnode()
{
for(int i=0;i<26;i++)
tree[cnt][i]=-1;
end_[cnt++]=0;
return cnt-1;
}
void init()
{
cnt=0;
root=newnode();
}
void insert_(char str[],int xpos)
{
int len= strlen(str);
int pos=root;
for(int i=0;iint id=str[i]-'a';
if(tree[pos][id]==-1)
tree[pos][id]=newnode();
pos=tree[pos][id];
}
if(end_[pos]==0)
{
v[pos].clear();
end_[pos]=1;
}
v[pos].push_back(xpos);//可能B中某些行相同,所以存到一起
}
void build()
{
queue<int> que;
fail[root]=root;
for(int i=0;i<26;i++)
{
if(tree[root][i]==-1) tree[root][i]=root;
else
{
fail[tree[root][i]]=root;
que.push(tree[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[now][i]==-1)
tree[now][i]=tree[fail[now]][i];
else
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
}
}
}
void query(char str[],int xpos)
{
int len=strlen(str);
int now=root;
for(int i=0;i'a'];//在Trie上不断向后跑
int temp=now;
while(temp!=root)
{
if(end_[temp])
{
for(int j=0;jint pp=v[temp][j];
con[xpos][i-y+1][pp]=true;//列的起始位置是i-y+1
}
}
temp=fail[temp];
}
}
return ;
}
};
char str[maxn*2];
char str1[1005][1005];
ACTrie ac;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;iscanf("%s",str1[i]);
ac.init();
scanf("%d%d",&x,&y);
for(int i=0;iscanf("%s",str);
ac.insert_(str,i);
}
ac.build();
for(int i=0;ifor(int j=0;jfor(int k=0;kfalse;
}
}
}
for(int i=0;iint ans=0;
for(int i=0;i<=n-x;i++)//注意边界
{
for(int j=0;j<=m-y;j++)//注意边界
{
int flag=0;
for(int k=i,l=0;kif(!con[k][j][l])
{
flag=1;
break;
}
}
if(flag==0) ans++;//全都匹配
}
}
printf("%d\n",ans);
}
return 0;
}
未完待续…