「CTS2019 | CTSC2019」重复(Kmp)

「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);
}










原文地址:https://www.cnblogs.com/chasedeath/p/12936536.html