BZOJ 1009 KMP思想 + DP + 矩阵快速幂
第一次用MarkDown和LaTex,写得有点丑……
本题的坑爹历程给了我一个血的教训:没有真正搞清楚做法之前,不要瞎BB地写题解。不然会造成深陷坑中的严重后果。
题意简述:给定一个字符串s,求出长度为n的不含字串s的字符串t的数量。
这道题是一个非常经典的模型,DP之:
设 f[i][j]为前i个t字符,匹配到s的第 j位(强制选 i)的方案数,则有
ans=Σ( f[n][k] | 0≤k<m )
接下来考虑 f[i][j]的转移:
由于我们是要统计数量,所以我们可以枚举第 i+1位是几。然后自然地,应该转移到(这里亦即贡献到)f[i+1][pos],其中pos为加上这个字符之后,当前串匹配到的位置。
然后我们发现,每一轮i=>i+1的转移都是一样的。对于这样的“无脑”递推,我们可以构造矩阵,然后使用矩阵快速幂来加速。如何构造矩阵呢?我们考虑矩阵元素M[i][j]的意义。它会与当前向量的第j个维度相乘,然后贡献到下一个向量的第i个维度。因此,如果有f[i+1][pos] += f[i][j],就应把M[pos][j]加1。不难发现,处理好这个矩阵之后,它的第0列其实就是初始向量。所以,只需做n次矩阵乘法即可。
代码略丑:
// BZOJ 1009 KMP+Matrix
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=25, N=M;
#define rep(i,a,b) for (int i=a; i<=b; i++)
#define dep(i,a,b) for (int i=a; i>=b; i--)
#define read(x) scanf("%d", &x)
#define fill(a,x) memset(a, x, sizeof(a))
int mod;
struct Matrix {
int n, m, a[N][M];
void init(int n, int m) {
this->n=n;
this->m=m;
fill(a, 0);
}
Matrix operator * (const Matrix B) const {
Matrix ret;
ret.init(B.n, B.n);
rep(i,0,n-1) rep(j,0,n-1) rep(k,0,n-1) ret.a[i][j]=(ret.a[i][j]+a[i][k]*B.a[k][j])%mod;
return ret;
}
Matrix operator ^ (const int k) const {
Matrix ret, t=*this;
ret.n=n; ret.m=m;
int tk=k;
fill(ret.a, 0);
rep(i,0,ret.n-1) ret.a[i][i]=1;
while (tk) {
if (tk&1) ret=ret*t;
t=t*t;
tk>>=1;
}
return ret;
}
} A;
int f[M], n, m;
char st[M];
void get_fail() {
f[0]=f[1]=0;
int j=0;
rep(i,2,m) {
while (j && st[j+1]!=st[i]) j=f[j];
if (st[j+1]==st[i]) j++;
f[i]=j;
}
}
int main()
{
read(n); read(m); read(mod);
scanf("%s", st+1);
A.init(m, m);
get_fail();
rep(i,0,m-1)
rep(j,0,9) {
int pos=i;
while (pos && st[pos+1]-'0'!=j) pos=f[pos];
if (st[pos+1]-'0'==j) pos++;
if (pos!=m) A.a[pos][i]=(A.a[pos][i]+1)%mod;
}
A=A^n;
int ans=0;
rep(i,0,m-1) ans=(ans+A.a[i][0])%mod;
printf("%d\n", ans);
return 0;
}