题目大意
给出一个长度为 (m) 的数字串 (A) , 问有多少个长度为 (n) 的数字串 (X) ,满足 (A) 不为 (X) 的子串,请对答案 (mod M) 。
(1 leq n leq 10^9) , (1 leq m leq 20) , (1 leq M leq 1000) , (0 leq A_i,X_i leq 9) 。
题解
设 (f(i,k)) 表示 (X) 前 (i) 位中最后 (k) 位匹配到 (A) 的前 (k) 位,即 (X_{i} = A_{k},X_{i - 1} = A_{k- 1},dots,X_{i - k+1} = A_1) ,且前 (i) 位没有某一段子串与 (A) 匹配成功时, (X) 的前 (i) 位的方案数。
设 (g(k,j)) 表示匹配到 (A) 的前 (k) 位,加入一个字符后,变为匹配到 (A) 的前 (j) 位的方案数。
容易得到
[f(i,j) = sumlimits_{k = 0}^{m - 1}f(i-1,k) imes g(k,j)
]
(g(k,j)) 可以用 KMP 预处理,时间复杂度为 (O(m^2)) 。
然后直接进行上面的 dp ,时间复杂度是 (O(nm^2)) ,显然过不了。
则答案为 (sumlimits_{i=0}^{m-1} f(n,i)) 。
观察状态转移方程,发现长得很像矩阵的乘法运算。
所以我们可以把 (f) 和 (g) 看做矩阵 (F) 和 (G) ,即 (F_i = F_{i-1}G) 。
所以 (F_n = F_0 G^n) 。
写一个矩阵快速幂就可以把时间复杂度降为 (O(m^2 log n)) 。
#include <cstdio>
#include <cstring>
#define MAX_M (20 + 5)
int n, m, mod;
struct Matrix
{
int r, c;
int mat[MAX_M][MAX_M];
Matrix()
{
r = 0;
c = 0;
memset(mat, 0, sizeof mat);
}
Matrix operator * (Matrix x)
{
Matrix res;
res.r = r;
res.c = x.c;
for (int i = 0; i < r; ++i)
{
for (int j = 0; j < x.c; ++j)
{
for (int k = 0; k < c; ++k)
{
res.mat[i][j] += mat[i][k] * x.mat[k][j];
}
res.mat[i][j] %= mod;
}
}
return res;
}
};
Matrix Pow(Matrix x, int y)
{
Matrix res;
res.r = res.c = m;
for (int i = 0; i < m; ++i)
{
res.mat[i][i] = 1;
}
while (y)
{
if (y & 1) res = res * x;
x = x * x;
y >>= 1;
}
return res;
}
char s[MAX_M];
int nxt[MAX_M];
Matrix f, g;
int ans;
int main()
{
scanf("%d%d%d%s", &n, &m, &mod, s + 1);
f.r = 1;
f.c = g.r = g.c = m;
for (int i = 1, j = 0; i <= m; ++i)
{
while (j && s[i + 1] != s[j + 1]) j = nxt[j];
if (s[i + 1] == s[j + 1]) ++j;
nxt[i + 1] = j;
}
for (int i = 0; i < m; ++i)
{
for (char ch = '0'; ch <= '9'; ++ch)
{
for (int j = i; ; j = nxt[j])
{
if (s[j + 1] == ch)
{
++g.mat[i][j + 1];
break;
}
if (!j)
{
++g.mat[i][j];
break;
}
}
}
}
f.mat[0][0]= 1;
f = f * Pow(g, n);
for (int i = 0; i < m; ++i)
{
ans += f.mat[0][i];
}
ans %= mod;
printf("%d", ans);
return 0;
}