「CTS2019」重複(KMP自動機dp)

傳送門.


我這麼菜怎麼可能會標算的神仙解法?

我們發現如果直接考慮有一個子串<S的話,是很有難度的。

不妨轉換爲沒有子串<S,也就是把T丟到S的KMP自動機 上,一直跑,注意只能走合法邊。

合法的意思是假設現在匹配了S[1…x],新加一個字符c,不存在s[1..y]=s[xy+1..x]s[y+1]&gt;c(y=0)s[1..y]=s[x-y+1..x],且s[y+1]&gt;c(y可以=0)

那這個東西怎麼計數呢?

如果已經有無限個T拼起來,再加一個T,在自動機上的點不會變。

也就是從自動機上一個點出發,走完T之後,回到原點的方案數。

那麼只需要枚舉起點,就可以得到一個O(n2m)O(n^2m)的做法。

再想想有什麼美妙的性質,一個點的合法出邊中,只有至多一條不是轉移到0的,這個結論可以由合法邊的判定得到。

那麼枚舉這個點走了多少步走到0,後面的事情只用對0做一個預處理的dp。

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i <  B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 2005;

int m, n;
char s[N];
int nt[N], to[N][26];

const int mo = 998244353;

ll f[N][N], ans;

int main() {
	freopen("repeat.in", "r", stdin);
	freopen("repeat.out", "w", stdout);
	scanf("%d", &m);
	scanf("%s", s + 1); n = strlen(s + 1);
	fo(i, 1, n) s[i] -= 'a';
	int x = 0;
	fo(i, 2, n) {
		while(x && s[x + 1] != s[i]) x = nt[x];
		x += s[x + 1] == s[i];
		nt[i] = x;
	}
	fo(i, 0, n) {
		fo(j, 0, 25) {
			to[i][j] = -1; int ky = 1;
			int x = i == n ? nt[i] : i;
			while(x) ky &= s[x + 1] <= j, x = nt[x];
			ky &= s[x + 1] <= j;
			x = i == n ? nt[i] : i;
			while(x && s[x + 1] != j) x = nt[x];
			x += s[x + 1] == j;
			if(ky) to[i][j] = x;
		}
	}
	f[0][0] = 1;
	fo(i, 0, m - 1) {
		fo(j, 0, n) if(f[i][j]) {
			fo(c, 0, 25) if(to[j][c] != -1) {
				f[i + 1][to[j][c]] += f[i][j];
			}
		}
		fo(j, 0, n) f[i + 1][j] %= mo;
	}
	ans = 1; fo(i, 1, m) ans = ans * 26 % mo;
	fo(i, 0, n) {
		int x = i;
		fo(t, 0, m - 1) {
			fo(c, 0, 25) if(to[x][c] == 0)
				ans -= f[m - (t + 1)][i];
			int y = -1;
			fo(c, 0, 25) if(to[x][c] > 0)
				y = to[x][c];
			x = y;
			if(y == -1) break;
		}
		if(x == i) ans --;
	}
	ans = (ans % mo + mo) % mo;
	pp("%lld\n", ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章