该题与\(IndeedTokyo2019\)校招笔试题涉及密码有相同的思路,都是\(DP\)问题。
由于状态的数量众多,所以我们需要使用状态机模型考虑一大类状态的转移。
使用闫氏\(DP\)分析法,从集合角度分析问题:
当我们计算中间矩阵\(a\)时,矩阵乘法公式\(f[i+1,k]=\sum^{m-1} _ {j=0}f[i,j] * a[j,k]\),我们我们只需要知道\(^{[1]}\)不吉利数字的长度为\(j\)的前缀加上数字\(0-9\)之后能转移到哪个长度\(k\),然后使\(a[j,k]+1\)即可。
\([1]\):可以使用\(KMP\)来辅助优化。
优化思路:\(ne[j]=k\)表示下标为\(j\)的前面\(k\)个字符与前缀的\(k\)个字符相同,即表示当第\(j\)个字符不匹配时指针将要移动到的位置。所以我们要使第\(j\)个字符之后加上一个数字后与某个长度匹配,只需要使\(k=j+1\),表示前\(j\)个字符都已经匹配,当第\(k\)个字符不等于加上的数字时再移动指针,当指针不移动时就表示已经匹配到了一个长度为\(k\)的前缀,使\(a[j,k]+1\)即可。
const int N = 25; int a[N][N], ne[N]; char str[N]; int n, m, mod; void mul(int c[][N], int a[][N], int b[][N]) { static int temp[N][N]; memset(temp, 0, sizeof temp); for (int i = 0; i < m; i ++ ) for (int j = 0; j < m; j ++ ) for (int k = 0; k < m; k ++ ) temp[i][j] = (temp[i][j] + a[i][k] * b[k][j]) % mod; memcpy(c, temp, sizeof temp); } int qpow() { int F[N][N] = {1}; while (n) { if (n & 1) mul(F, F, a); mul(a, a, a); n >>= 1; } int res = 0; for (int i = 0; i < m; i ++ ) res = (res + F[0][i]) % mod; return res; } int main() { cin >> n >> m >> mod; cin >> str + 1; // kmp匹配ne数组 int j = 1, k = 0; ne[j] = k; while (j <= m) { if (k == 0 || str[j] == str[k]) j ++ , k ++ , ne[j] = k; else k = ne[k]; } // kmp初始化a数组 for (int j = 0; j < m; j ++ ) for (int c = '0'; c <= '9'; c ++ ) { int k = j + 1; while (k && str[k] != c) k = ne[k]; if (k < m) a[j][k] ++ ; } cout << qpow() << endl; return 0; }