洛谷P3193 [HNOI2008]GT考试 题解
题目链接:P3193 [HNOI2008]GT考试
题意:
阿申准备报名参加 GT 考试,准考证号为 $N$ 位数$X_1,X_2…X_n(0\le X_i\le9)$,他不希望准考证号上出现不吉利的数字。
他的不吉利数字$A_1,A_2…A_m(0\le A_i\le 9)$ 有 $M$ 位,不出现是指 $X_1,X_2…X_n$ 中没有恰好一段等于 $A_1,A_2…A_m$,$A_1$ 和$X_1$ 可以为 $0$$N\leq10^9,M\leq20,K\leq1000$
显然是矩阵快速幂优化dp(看数据范围)。
设 $f_{i,j}$ 表示考虑到第 $i$ 个数,匹配到 $X$ 的第 $j$ 位时的方案数,则
注意这里的 $0\le j < m$ ,因为我们不能让它包含完整的 $X$ 。
这个 $p$ 不一定是 $0$ 或 $j-1$ ,根据KMP的性质可以很容易发现。
看上去这个可以用KMP来搞搞,又长的很像矩阵快速幂的题目
于是尝试套路地构造出预处理数组 $g_{i,j}$ ,以产生矩阵快速幂的形式
设 $g_{i,j}$ 表示匹配到 $X$ 的第 $i$ 位,有多少种加字符 $0\sim9$ 的方法可以使其匹配到 $j$
注意到题目中 $X_1$ 是可以为 $0$ 的,因此不用单独考虑前导零的问题,则
注意这里 $k$ 的意义改变为了:枚举已经匹配的长度,尝试加一个字符转移至 $j$ 。
然后把式子看成这样的形式 $f^{i}_{j} =\sum f^{i-1}_{k} \times g_{k,j}$ ,显然与矩阵乘法的定义相同
我们把 $f$ 和 $g$ 看作两个矩阵(注意 $f$ 是行矩阵),则有
则
然后我们矩阵快速幂求一下就好啦
最后答案就是 $\sum\limits_{i=0}^{m-1} f^{n}_{i}$ ,没有 $m-1$ 的原因是,不能包含 $m$
时间复杂度 $O(m^3 \log n)$
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(25)
typedef vector< vector <int> > mat;
char s[N];
int n,m,mod,fail[N];
void add(int &x,int y){ (x+=y) >= mod ? x-=mod : 0;}
mat mul(mat A,mat B)
{
int n=A.size(),m=A[0].size(),p=B[0].size();
mat C(n,vector<int>(p));
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
for(int k=0; k<p; k++)
add(C[i][j],A[i][k]*B[k][j]%mod);
return C;
}
mat qpow(mat A,int b)
{
int n=max(A.size(),A[0].size());
mat B(n,vector<int>(n));
for(int i=0; i<n; i++) B[i][i]=1;
for(; b; b>>=1)
{
if(b&1) B=mul(B,A);
A=mul(A,A);
}
return B;
}
mat kmp()
{
for(int i=2,j=0; i<=m; i++)
{
while(j&&s[i]!=s[j+1]) j=fail[j];
if(s[i]==s[j+1])++j;
fail[i]=j;
}
mat A(m,vector<int>(m));
for(int i=0,j; i<m; i++)
for(int c='0'; c<='9'; c++)
{
j=i; while(j&&s[j+1]!=c) j=fail[j];
if(s[j+1]==c) ++j;
add(A[i][j],1);
}
return A;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m >> mod >> (s+1);
mat G=kmp(); G=qpow(G,n);
int res=0;
for(int i=0; i<m; i++) add(res,G[0][i]);
cout << res << '\n';
return 0;
}