「CTS2019 | CTSC2019」重复(Kmp)
Part1
首先我们考虑对于一个已经确定的\(t\)串,如何检查是否合法
对于\(s\)串建立\(\text{kmp}\)(\(\text{kmp}\)自动机当然可以),
如果当前\(\text{kmp}\)指针\(j\)在\(\text{fail}\)树上的祖先所对应的所有下一个位置\(s[ancestor+1]\)中,存在一个字符,\(t\)中当前位置的字符\(t[i]<s[ancestor+1]\)
那么就是存在一个"有意义的串",并且这个串和s串第一个不同的位置就是\(ancestor+1\)
所以可以预处理一个\(fail\)树上祖先最大的\(s[ancestor+1],Max[state]\)
rep(i,1,n) Max[i-1]=s[i];
Max[n]='a';//边界条件
cmax(Max[1],Max[0]);
rep(i,2,n) {
int j=nxt[i-1];
while(j && s[i]!=s[j+1]) j=nxt[j];
if(s[i]==s[j+1]) ++j;
nxt[i]=j;
cmax(Max[i],Max[j]);
}
//预处理Max
rep(i,0,n) {
if(i<n) Trans[i][s[i+1]-'a']=i+1;
rep(j,0,25) if(j!=s[i+1]-'a') Trans[i][j]=Trans[nxt[i]][j];
}//kmp自动机,不要再暴力了
rep(i,m+1,n+m) t[i]=t[i-m]; // 延长到n+m就够了
int j=0,f=0;
rep(i,1,n+m){
if(t[i]<Max[j]) { // 存在一个位置满足即可
f=1;
break;
}
j=Trans[j][t[i]-'a'];
}
配合dfs枚举,可以拿到pts10
Part2
设状态转移为\(Trans[state][c]\)
把kmp状态压进dp状态里
如果把问题直接放到无穷意义下来看
那么跑够长度之后,后面的任意加入\(m\)次字符都会构成一个循环
枚举循环开始状态为\(st\),\(\text{dp}\)走了\(m\)步回到\(st\)的方案数
如果计算合法方案数,那么还要多一维,所以直接计算不合法方案,也就是
\(Trans[state][Max[state]..25]\)这一段转移是不会出现合法情况的
最后减一下
复杂度\(O(m\cdot n^2)\)
namespace pt2{
int dp[2][N];
void Solve(){
int cur=0,ans=0;
rep(st,0,n) { // 枚举初始状态
rep(i,0,n) dp[cur][i]=dp[!cur][i]=0;
dp[cur][st]=1;
rep(t,1,m) {
cur^=1;
rep(i,0,n) if(dp[!cur][i]){
rep(c,Max[i]-'a',25) {//只转移不合法的
dp[cur][Trans[i][c]]+=dp[!cur][i];
Mod1(dp[cur][Trans[i][c]]);
}
dp[!cur][i]=0;
}
}// 走m步回到st
ans+=dp[cur][st],Mod1(ans);
}
ans=(qpow(26,m)-ans+P)%P;//减一下
printf("%d\n",ans);
}
}
Part3
观察上面一档情况,合法的转移\(Trans[state][Max[state]..25]\)
如果枚举下一个字符\(c>Max[state]\),那么在\(s\)串中就找不到任何匹配了,下一个状态就是\(0\)
否则,下一个状态就是\(Trans[state][c]\)
也就是说,每个点出发其实只有两种情况,一种是一定会经过\(0\)的
所以对于这个环是否经过\(0\)进行讨论
如果不经过\(0\),那么考虑直接从\(st\)出发走\(m\)步非0转移即可
经过\(0\)的,预处理一个从\(0\)出发,走了\(i\)步第一次回到\(0\)的方案数
\([st\rightarrow 0,0\rightarrow 0,0\rightarrow 0...,0\rightarrow st]\)
长成这个样子的转移
枚举第一个\(st\rightarrow 0\)的时间,后面可以预处理出来
复杂度\(O(n\cdot m)\)
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define reg register
typedef long long ll;
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> void cmin(T &a, T b){ ((a>b)&&(a=b)); }
template <class T> void cmax(T &a, T b){ ((a<b)&&(a=b)); }
char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=2010,P=998244353;
int n,m;
char s[N];
int Max[N],nxt[N],Trans[N][26];
ll qpow(ll x,ll k=P-2) {
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
int st[N][N]; // 从i出发,走了j步第一次到0
int dp[N][N]; // 从0出发,走了i步,到了j,如果到0就不进行转移
int main(){
freopen("repeat.in","r",stdin),freopen("repeat.out","w",stdout);
m=rd();
scanf("%s",s+1),n=strlen(s+1);
rep(i,1,n) Max[i-1]=s[i];
Max[n]='a';
cmax(Max[1],Max[0]);
rep(i,2,n) {
int j=nxt[i-1];
while(j && s[i]!=s[j+1]) j=nxt[j];
if(s[i]==s[j+1]) ++j;
nxt[i]=j;
cmax(Max[i],Max[j]);
}
rep(i,0,n) {
if(i<n) Trans[i][s[i+1]-'a']=i+1;
rep(j,0,25) if(j!=s[i+1]-'a') Trans[i][j]=Trans[nxt[i]][j];
}
int ans=0;
st[0][0]++;
rep(i,1,n) {
int j=i,f=1;
rep(k,1,m) {
st[i][k]+=25-(Max[j]-'a'),j=Trans[j][Max[j]-'a'];
if(!j) {
st[i][k]++,f=0;
break;
}
}
if(j==i) ans+=f;
}
dp[0][0]=1;
rep(i,1,m) {
rep(j,0,n) if(dp[i-1][j]) {
dp[i][Trans[j][Max[j]-'a']]+=dp[i-1][j],Mod1(dp[i][Trans[j][Max[j]-'a']]);
dp[i][0]=(dp[i][0]+1ll*(25-(Max[j]-'a'))*dp[i-1][j])%P;
}
}
rep(i,0,n) { // 枚举初始状态
rep(j,0,m) if(st[i][j] && dp[m-j][i]) { // 枚举走了几步第一次到0
ans=(ans+1ll*st[i][j]*dp[m-j][i])%P;
}
}
ans=(qpow(26,m)-ans+P)%P;
printf("%d\n",ans);
}