[HYSBZ]1009[HNOI2008]GT考试

题意

字符串由0-9组成,每一位可以是0-9的任何一个数。
并且要求字符串不包含另一个给定的字符串,求方案数。

题解

一开始以为是数位dp,然后发现是一个dp。

(f[i][j])为前(i)个字符,最后(j)个字符与目标串匹配的方案数。
我们可以枚举下一个字符,考虑下一个字符会对匹配产生什么影响。

  • 匹配下一个字符,从(j)匹配到(j+1)
  • 无法匹配下一个字符,到之前找匹配

到之前找匹配的这个过程与kmp很像,其实就是不停的跳(nxt),不过其实第一个也就是kmp,转移的时候就不停跳匹配就行。

但是(nle 10^{12}),枚举肯定会爆炸。

注意到每一步的转移都是一样的,因为(nxt)的转移只跟(j)有关,而对于每个(i)都是重复的递推。

那么就可以用矩阵优化递推。

[f[i][j] = sum _{k = 0}^{kle m - 1} f[i][k] * g[k][j] ]

(g[i][j])表示末尾匹配到(i)时,有多少种取法使得末尾匹配到(j)

(g)数组可以在kmp的同时顺便求出来。

这样只要把(g)数组自乘(n)次即可,同时(f)数组的初始条件为(f[0][0] = 1),只要取(g)数组的第(0)行即可。

#include <bits/stdc++.h>
#define Mid (l + r << 1)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define int long long

using namespace std;

int read() {
	char c; int num, f = 1;
	while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
	while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
	return f * num;
}
int n, m, mod;
int a[29], nxt[29];
struct Mat {
	int g[29][29];
} A, B;
Mat operator * (const Mat &A, const Mat &B) {
	Mat C;
	for(int i = 0; i < m; i++)
		for(int j = 0; j < m; j++)
			C.g[i][j] = 0;
	for(int i = 0; i < m; i++) {
		for(int j = 0; j < m; j++) {
			for(int k = 0; k < m; k++) {
				C.g[i][j] = (C.g[i][j] + A.g[i][k] * B.g[k][j] % mod) % mod;
			}
		}
	}
	return C;
}
signed main()
{
	n = read(); m = read(); mod = read();
	for(int i = 1; i <= m; i++)
		scanf("%1d", &a[i]);
	int j = 0;
	for(int i = 2; i <= m; i++) {
		while(j && a[j + 1] != a[i]) j = nxt[j];
		if(a[j + 1] == a[i]) j++;
		nxt[i] = j;
	}
	for(int i = 0; i < m; i++) {
		for(int p = 0; p <= 9; p++) {
			int j = i;
			while(j && a[j + 1] != p) j = nxt[j];
			if(a[j + 1] == p) j++;
			A.g[i][j]++;
		}
	}
	for(int i = 0; i < m; i++)
		for(int j = 0; j < m; j++)
			B.g[i][j] = (i == j) ? 1 : 0;
	int x = n;
	for( ; x; x >>= 1, A = A * A)
		if(x & 1)
			B = A * B;
	int ans = 0;
	for(int i = 0; i < m; i++)
		ans = (ans + B.g[0][i]) % mod;
	printf("%lld
", ans);
	return 0;
}
原文地址:https://www.cnblogs.com/onglublog/p/14516136.html