题目传送门(luogu)
看到计数,容易想到dp。
像这种字符串匹配有关的dp一般都有一维是自动机上的状态(套路)。因为要构建长度为n的准考证号所以还有一维长度。
这样我们就知道了子状态:$dp[i][j]$代表长度为i的准考证号与不吉利数字匹配长度为j的方案数。
很容易想到转移方程:$dp[i][j]=sum_{k=0}^{m-1}dp[i-1][k] imes g[k][j]$。其中$g[k][j]$代表加一个字符从状态k到状态j的方案数。
先来看怎么求这个g,这就要用到kmp自动机。
枚举第一个状态i,然后再枚举在后面加的字符c,如果kmp自动机已经建出来了则可以知道加一个字符可以从i到j,令$g[i][j]++$。
ch[0][s[1] - '0'] = 1; for (int i = 0; i < m; ++i) { for (char k = '0'; k <= '9'; ++k) { if (k == s[i + 1]) ch[i][k - '0'] = i + 1; else ch[i][k - '0'] = ch[nxt[i]][k - '0']; g.arr[i][ch[i][k - '0']]++; } }
如果暴力去跑刚才的dp的复杂度是$O(nm^2)$的,肯定会炸。
我们发现每次转移都只和i-1和g有关,而g有是不变的,所以可以用矩阵加速转移。
/* dp[i][j]=dp[i-1][k] * g[k][j] */ #include <bits/stdc++.h> using namespace std; struct Matrix{ int arr[21][21], size; }g, dp; int n, m, mod; char s[21]; int nxt[21]; int ans; int ch[21][10]; void init(Matrix &ret, int size) { ret.size = size; for (int i = 0; i <= size; i++) { for (int j = 0; j <= size; j++) { ret.arr[i][j] = 0; } } } Matrix Mul(Matrix A, Matrix B) { Matrix C; init(C, A.size); for (int i = 0; i <= A.size; i++) { for (int j = 0; j <= A.size; j++) { for (int k = 0; k <= A.size; k++) { C.arr[i][j] = (C.arr[i][j] + (A.arr[i][k] * B.arr[k][j]) % mod) % mod; } } } return C; } Matrix ksm(Matrix A, int b) { Matrix ret; init(ret, A.size); for (int i = 0; i <= ret.size; i++) ret.arr[i][i] = 1; while (b) { if (b & 1) ret = Mul(ret, A); A = Mul(A, A); b >>= 1; } return ret; } int main() { scanf("%d%d%d", &n, &m, &mod); scanf("%s", s + 1); for (int i = 2, j = 0; i <= m; i++) { while (j && s[j + 1] != s[i]) j = nxt[j]; if (s[j + 1] == s[i]) j++; nxt[i] = j; } init(g, m - 1); ch[0][s[1] - '0'] = 1; for (int i = 0; i < m; ++i) { for (char k = '0'; k <= '9'; ++k) { else ch[i][k - '0'] = ch[nxt[i]][k - '0']; g.arr[i][ch[i][k - '0']]++; } } init(dp, m - 1); dp.arr[0][0] = 1; dp = Mul(dp, ksm(g, n)); for (int i = 0; i < m; i++) { ans = (ans + dp.arr[0][i]) % mod; } printf("%d", ans); return 0; }