题目链接
题目大意:给定一个文本串,n个模式串,其中模式串有两种,一种是同一模式串可以有重合的部分,如ababa 出现两次aba,另一种是相同的模式串不允许重合,如ababa 出现1次aba.问所有的模式串各自在文本串中出现了多少次.
思路:对于第一种,可以重合的模式串,就是朴素的ac自动机。
对于第二种,相同模式串不能重合,我们可以知道,相同模式串有重合当且仅当第二次出现的位置-第一次出现的位置<字符串长度,
我们可以开一个数组last[],last[i]记录以i号节点结尾的单词,最新匹配成功的位置 如在ababa中 ab第一次匹配的位置是 index=1 第二次 index=3 ,分别是2个b的下标,每次匹配到这个单词,判断一下 当前位置now-len>=last[i] 是的话就可以ans+1 .匹配过程基本上还是ac自动机中query函数的流程
void query(char *s)
{
int len=strlen(s),root=0;
for(int i=0;i<len;i++)
{
int u=tree[root][s[i]-'a'];
root=u;
while(u)
{
//ans[u][0]代表允许重合的情况,ans[u][1]代表不允许重回的情况,都算出来更简洁
if(isstr[u]) ans[u][0]++;
//isstr[u]: u结尾的路径是模式串的话,存单词长度,否则是0
if(isstr[u] && i-last[u]>=isstr[u] )
{
ans[u][1]++;
last[u]=i;//更新u结尾的模式串,在文本串中最新出现的位置
}
u=fail[u];
}
}
}
具体细节见代码注释
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define INF 1000000
#define LL unsigned long long
const int maxn=6e5+100;
int tot;//结点数 从1开始
int isstr[maxn];//结尾的长度
int tree[maxn][26];
int fail[maxn];
char s1[maxn];//文本串
int kinds[maxn/6];//第i个模式串的种类 可重合/不可
int ans[maxn][2];//节点最终的答案
int num[maxn/6];//每个单词的尾节点编号
int last[maxn];//以其结尾的单词上次匹配成功的位置
void Init()
{
memset(isstr,0,sizeof(isstr));
memset(fail,0,sizeof(fail));
memset(tree,0,sizeof(tree));
tot=0;
memset(ans,0,sizeof(ans));
memset(num,0,sizeof(num));
fill(last,last+maxn,-1);//初始化成-1 初始化成0的话abab里面第一处ab就漏记了
}
int Insert(char* s)
{
int len=strlen(s);
int root=0;
for(register int i=0;i<len;i++)
{
int id=s[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
isstr[root]=len;
return root;//返回节点编号
}
void getfail()
{
queue<int> q;
//初始化第二层,fail指针全指向根节点,入队
for(int i=0;i<26;i++)
{
if(tree[0][i])
{
q.push(tree[0][i]);
}
}
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<26;i++)
{
//如果这个孩子存在,它的fail指向的就是父亲的fail所指向节点的孩子
if(tree[v][i])
{
fail[tree[v][i]]=tree[fail[v]][i];
q.push(tree[v][i]);
}
//否则,让这个孩子指向父亲的fail所指向节点的孩子,便于查询
else tree[v][i]=tree[fail[v]][i];
}
}
}
void query(char *s)
{
int len=strlen(s),root=0;
for(int i=0;i<len;i++)
{
int u=tree[root][s[i]-'a'];
root=u;
while(u)
{
//ans[u][0]代表允许重合的情况,ans[u][1]代表不允许重回的情况,都算出来更简洁
if(isstr[u]) ans[u][0]++;
//isstr[u]: u结尾的路径是模式串的话,存单词长度,否则是0
if(isstr[u] && i-last[u]>=isstr[u] )
{
ans[u][1]++;
last[u]=i;//更新u结尾的模式串,在文本串中最新出现的位置
}
u=fail[u];
}
}
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
int cnt=1;
while(~scanf("%s",s1))
{
Init();
int n;
scanf("%d",&n);
char str[10];
for(int i=1;i<=n;i++)
{
scanf("%d%s",kinds+i,str);
num[i]=Insert(str);
}
getfail();
query(s1);
printf("Case %d\n",cnt++);
for(int i=1;i<=n;i++) printf("%d\n",ans[num[i]][kinds[i]]);
putchar('\n');
}
system("pause");
return 0;
}