A Secret HDU - 6153 扩展KMP || KMP
题目链接:https://vjudge.net/problem/HDU-6153
题意
求一个串T的所有后缀在串S中出现的次数.
给定母串str1,匹配串str2
str2[i-n]在str1中出现的次数为num[i]
str2[i-n]的长度为len[i]
求\(∑(num[i]×len[i])\) (0<=i<=n,n为strlen(str2)-1) 。
扩展KMP解法
可以利用拓展KMP求出S的每一个后缀和T的最长公共前缀。
假如当前最长公共前缀为k,就说明长度为k的前缀在S中出现了一次,并且这个k前缀不能构成k+1前缀。用一个cnt数组将各种长度前缀出现的次数记录下来。
对于样例2
abababab
aba
首先将样例翻转,得到
babababa
aba
拓展KMP求出的extend数组值如下
extend[0] = 0
extend[1] = 3
extend[2] = 0
extend[3] = 3
extend[4] = 0
extend[5] = 3
extend[6] = 0
extend[7] = 1
所以cnt数组值为
cnt[1] = 1
cnt[2] = 0
cnt[3] = 3
根据cnt数组来求T的前缀在S中出现的次数:
-
长度为3的前缀:出现了3次不解释。
-
长度为2的前缀:在长度为3的前缀中出现过3次,不能构成3前缀的2前缀数目(即cnt[2])等于0,所以2前缀出现了3次。
-
长度为1的前缀:在长度为2的前缀中出现了3次,不能构成2前缀的1前缀数目(即cnt[1])等于1,所以1前缀出现了4次。
问题解决
对于每一个点extend等差数列求和 累加就是答案:
因为每个点extend相当于
\(num[1]×len[1]+num[2]×len[2]+...+num[ extend[i] ]×len[ extend[i] ]\)
\(=1×1+1×2+...+1×extend[i]\)
\(=extend[i]×\frac{extend[i]+1}{2}\)
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
const int mod=1e9+7;
string s,t;
int slen,tlen;
int z[N];
int ext[N];
int cnt[N];
void get_Z(){
int l=0,r=0;
z[0]=tlen;
for(int i=1;i<tlen;i++){
if(i>=r){
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
else if(z[i-l]<r-i)
z[i]=z[i-l];
else{
z[i]=r-i;
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
}
}
void get_exKMP(){
int l=0,r=0;
while (r < slen && r < tlen && s[r] == t[r])
r++;
ext[0] = r;
// r=0;
for(int i=1;i<slen;i++){
if(i>r){
while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
ext[i]++;
l=i,r=i+ext[i];
}else if(z[i-l]<r-i) ext[i]=z[i-l];
else {
ext[i]=r-i;
while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
ext[i]++;
l=i,r=i+ext[i];
}
}
}
void solve(){
memset(z,0,sizeof z);
memset(ext,0,sizeof ext);
cin>>s>>t;
slen=s.length(),tlen=t.length();
reverse(t.begin(),t.end());
reverse(s.begin(),s.end());
get_Z();
get_exKMP();
int ans=0;
for(int i=0;i<slen;i++){
ans=(ans+(ext[i]*(ext[i]+1)/2)%mod)%mod;
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--)
solve();
return 0;
}
KMP解法
KMP:和拓展KMP相似。
主要思路就是求出不能构成k + 1前缀的k前缀数目。
和 用KMP统计T串在S串中出现次数 的代码相似,KMP匹配的时候,我们只需要在失配和匹配完成的时候记录一下即可。
设失配的时候已经匹配的长度为k,那么这个k前缀不能构成k + 1前缀。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
const int mod=1e9+7;
string s,t;
int slen,tlen;
int ne[N];
int cnt[N];
void get_next(){
ne[0]=-1;
int pre=-1,j=0;
while(j<tlen){
//不能使用优化
if(pre==-1 || t[pre]==t[j]){
ne[++j]=++pre;
}
else pre=ne[pre];
}
}
void kmp(){
get_next();
int i=0,j=0;
// 注意不能是<,否则最后一个模式会遗漏
//可以模拟第二个样例得出
while(i<=slen){
if(j==-1 || s[i]==t[j])
i++,j++;
else {
cnt[j]++;
j=ne[j];
}
if(j==tlen) {
cnt[j]++;
j=ne[j];
}
}
}
void solve(){
memset(cnt,0,sizeof cnt);
cin>>s>>t;
slen=s.length(),tlen=t.length();
reverse(t.begin(),t.end());
reverse(s.begin(),s.end());
kmp();
int ans=0;
for(int i=tlen;i>0;i--){
cnt[i]=(cnt[i]+cnt[i+1])%mod;
ans=(ans+cnt[i]*i)%mod;
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;cin>>t;
while(t--)
solve();
return 0;
}