Luogu 3193 [HNOI2008]GT考试
BZOJ1009
妙!
推荐这篇题解: https://www.luogu.org/blog/Edgration/solution-p3193
考虑设计dp,设$f_{i, j}$表示长串匹配到i,短串匹配到j的方案数,初值有$f_{0,0} = 1$
那么最后的答案 $ans = \sum_{i = 0}^{m - 1} f_{n,i}$
考虑转移,假设当前填到第i位,有一种填法能使$f_{i,j}$转移到$f_{i + 1, j + 1}$,那么填剩下的数字全部都转移到$f_{i + 1,0}$吗?
错!这就是一开始想错的地方,填不一样的数字并不一定是转移到0的匹配位置,而是考虑转移到以j结尾的后缀的最长前缀!
设$g_{i, j}$表示从i的匹配长度转移到j的匹配长度的方案数,有转移:
$f_{i, j} = \sum_{k = 0}^{m - 1}f_{i - 1, k} * g_{k, j}$
因为给出的短串是恒定的,所以g数组的值也是恒定的,而找与后缀相匹配的最长前缀,肯定是想到kmp啦
然而这样还是不足以通过本题,再次观察这个方程,发现这就是一个矩阵乘法的形式,相当于把f看成一个1*m的矩阵F,把g看成一个m*m的转移矩阵G。
$F' = F * G^{n}$ 用G转移Fn次
到此为止,本题全部解决,时间复杂度$O(m^{2}logn)$
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 25; int n, m, P, nxt[N], mat[N][N]; char str[N]; inline void prework() { nxt[1] = nxt[0] = 0; for(int i = 2, j = 0; i <= m; i++) { for(; j > 0 && str[i] != str[j + 1]; j = nxt[j]); if(str[i] == str[j + 1]) j++; nxt[i] = j; } for(int i = 0; i < m; i++) { for(int j = '0'; j <= '9'; j++) { int tmp = i; for(; tmp > 0 && str[tmp + 1] != j; tmp = nxt[tmp]); if(str[tmp + 1] == j) tmp++; if(tmp < m) mat[i][tmp]++; } } } inline void work(int &x, int y) { x = (x + y % P) % P; } struct Matrix { int s[N][N]; inline void init() { memset(s, 0, sizeof(s)); } friend Matrix operator * (const Matrix &x, const Matrix &y) { Matrix res; res.init(); for(int i = 0; i < m; i++) for(int j = 0; j < m; j++) for(int k = 0; k < m; k++) work(res.s[i][j], x.s[i][k] * y.s[k][j]); return res; } inline Matrix pow(int y) { Matrix res = *this, x = *this; for(y--; y > 0; y >>= 1) { if(y & 1) res = res * x; x = x * x; } return res; } } f, g; int main() { scanf("%d%d%d", &n, &m, &P); scanf("%s", str + 1); prework(); for(int i = 0; i < m; i++) for(int j = 0; j < m; j++) g.s[i][j] = mat[i][j]; g = g.pow(n); f.s[0][0] = 1; f = f * g; int ans = 0; for(int i = 0; i < m; i++) work(ans, f.s[0][i]); printf("%d\n", ans); return 0; }