「CTS2019」重复(KMP自动机dp)

传送门.


我这么菜怎么可能会标算的神仙解法?

我们发现如果直接考虑有一个子串<S的话,是很有难度的。

不妨转换为没有子串<S,也就是把T丢到S的KMP自动机 上,一直跑,注意只能走合法边。

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

那这个东西怎么计数呢?

如果已经有无限个T拼起来,再加一个T,在自动机上的点不会变。

也就是从自动机上一个点出发,走完T之后,回到原点的方案数。

那么只需要枚举起点,就可以得到一个 O ( n 2 m ) O(n^2m) O(n2m)的做法。

再想想有什么美妙的性质,一个点的合法出边中,只有至多一条不是转移到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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值