P5404-[CTS2019]重复【KMP,dp】
正题
题目链接:https://www.luogu.com.cn/problem/P5404
题目大意
给出一个字符串\(S\),然后求有多少个长度为\(m\)的串\(T\)满足。无限多个串\(T\)拼接起来后能找出一个长度和\(S\)相等的子串字典序比\(S\)小。
\(1\leq |S|,m\leq 2000\)
解题思路
首先有一个小于的很难找,所以我们找有多少一直大于等于的减去就好了。
然后其实如果有一个大于位置大于\(S\)串匹配就可以直接不管,所以其实我们主要考虑前面都相等的情况,(根据题解)考虑用\(KMP\)。
设我们现在匹配到\([1,k]\),然后有\([1,nxt_k]=[k-nxt_k+1,k]\),然后加了一个字符如果有跳的边而且是转移边里面字符最大的,因为我们显然需要匹配出一个最大的前缀不然不能保证有小于的时候能直接找到。
而且如果我们现在在\(KMP\)上走了\(T^{\infty}\)之后节点是\(i\),那么\(T^{\infty}T\)也是会匹配回到节点\(i\)的,所以相当于我们要找一个节点\(p\)使得它匹配了\(T\)之后仍然是回到节点\(p\)。
暴力枚举节点来\(dp\)肯定是会\(T\),考虑优化一下。
不难发现如果一个点走\(m\)步之后没有回到过\(0\)号节点的话方案只有一种(因为每个点连接\(0\)以外的出边最多只有一条)。
所以设\(f_{i,j}\)表示从\(0\)出发走\(j\)步到达\(i\)的方案数。
然后对于起点枚举多少步后走到\(0\)再用\(f\)统计答案就好了。
时间复杂度\(O(nm)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2100,P=998244353;
ll n,m,ans,nxt[N],ch[N][26],f[N][N],mx[N];
char s[N];
signed main()
{
scanf("%lld%s",&m,s+1);
n=strlen(s+1);ans=1;
for(ll i=1;i<=m;i++)ans=ans*26ll%P;
for(ll i=2,j=0;i<=n;i++){
while(j&&s[i]!=s[j+1])j=nxt[j];
j+=(s[i]==s[j+1]);nxt[i]=j;
}
for(ll i=0;i<=n;i++)
for(ll c=0;c<26;c++){
if(s[i+1]==c+'a')ch[i][c]=i+1;
else ch[i][c]=ch[nxt[i]][c];
if(ch[i][c])mx[i]=c;
}
f[0][0]=1;
for(ll i=0;i<m;i++)
for(ll j=0;j<=n;j++)
for(ll c=mx[j];c<26;c++)
(f[ch[j][c]][i+1]+=f[j][i])%=P;
for(ll i=0;i<=n;i++){
ll x=i;
for(ll j=1;j<=m;j++){
(ans-=(25-mx[x])*f[i][m-j]%P)%=P;
x=ch[x][mx[x]];
if(!x)break;
}
if(i&&x==i)(ans+=P-1)%=P;
}
printf("%lld\n",(ans+P)%P);
return 0;
}