【BZOJ】1009: [HNOI2008]GT考试(dp+矩阵乘法+kmp+神题)
http://www.lydsy.com/JudgeOnline/problem.php?id=1009
好神的题orzzzzzzzzzz
首先我是连递推方程都想不出的人。。。一直想用组合来搞。。看来我是sb。。
设f[i,j]表示前i个字符匹配了前j个不吉利数字的方案,即i-j+1~i都是不吉利数字
那么答案就是sigma{f[n,i], 0<=i<m}
转移是
f[i+1,k]=sum{f[i, j],枚举i+1的字符后,k是i+1字符和不吉利数字匹配1~k,0<=k<=j}
发现k可以由kmp一样的适配数组得到
而我们发现,每一个阶段i~i+1的转移都是枚举i+1然后找j的失配,也就是说,所有的转移都是一样的。
方程又是求和,那么可以考虑矩阵乘法优化(orzzzzz
即根据
$$c[i,j]=\sum a[i,k] \times b[k,j]$$
则状态f[i+1]和f[i]的矩阵转移可看做
$$f_{i+1}[1, j]=\sum f_{i}[1,k] \times A[k, j]$$
所以我们可以逆推出矩阵$A$,即它表示的意思是从k转移到j上的倍数
所以我们可以kmp一次不吉利数字,求出$A$,然后就可以矩乘logn求出$A^n$做出本题
最后的答案是求出的$A^n$后,乘上$f_{1}$得到$f_n$然后累计$f_n[1, i], 0<=i<m$
#include <cstdio> #include <cstring> #include <cmath> #include <string> #include <iostream> #include <algorithm> #include <queue> #include <set> #include <map> using namespace std; typedef long long ll; #define pii pair<int, int> #define mkpii make_pair<int, int> #define pdi pair<double, int> #define mkpdi make_pair<double, int> #define pli pair<ll, int> #define mkpli make_pair<ll, int> #define rep(i, n) for(int i=0; i<(n); ++i) #define for1(i,a,n) for(int i=(a);i<=(n);++i) #define for2(i,a,n) for(int i=(a);i<(n);++i) #define for3(i,a,n) for(int i=(a);i>=(n);--i) #define for4(i,a,n) for(int i=(a);i>(n);--i) #define CC(i,a) memset(i,a,sizeof(i)) #define read(a) a=getint() #define print(a) printf("%d", a) #define dbg(x) cout << (#x) << " = " << (x) << endl #define error(x) (!(x)?puts("error"):0) #define printarr2(a, b, c) for1(_, 1, b) { for1(__, 1, c) cout << a[_][__]; cout << endl; } #define printarr1(a, b) for1(_, 1, b) cout << a[_] << '\t'; cout << endl inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; } inline const int max(const int &a, const int &b) { return a>b?a:b; } inline const int min(const int &a, const int &b) { return a<b?a:b; } const int N=22; typedef int mtx[N][N]; mtx a, t, f, b; int n, m, MD, p[N]; char s[N]; void mul(mtx a, mtx b, mtx c, int la, int lb, int lc) { rep(i, la) rep(j, lc) { t[i][j]=0; rep(k, lb) t[i][j]=(t[i][j]+(a[i][k]*b[k][j])%MD)%MD; } rep(i, la) rep(j, lc) c[i][j]=t[i][j]; } int main() { read(n); read(m); read(MD); scanf("%s", s+1); int j=0; for(int i=2; i<=m; ++i) { while(j && s[i]!=s[j+1]) j=p[j]; if(s[i]==s[j+1]) ++j; p[i]=j; } rep(i, m) for1(k, 0, 9) { j=i; while(j && s[j+1]-'0'!=k) j=p[j]; if(s[j+1]-'0'==k) ++j; if(j<m) a[i][j]=(a[i][j]+1)%MD; } rep(i, m) b[i][i]=1; while(n) { if(n&1) mul(b, a, b, m, m, m); mul(a, a, a, m, m, m); n>>=1; } int ans=0; f[0][0]=1; mul(f, b, f, 1, m, m); rep(i, m) ans=(ans+f[0][i])%MD; print(ans); return 0; }
Description
阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。他的不吉利数学A1A2...Am(0<=Ai<=9)有M位,不出现是指X1X2...Xn中没有恰好一段等于A1A2...Am. A1和X1可以为0
Input
第一行输入N,M,K.接下来一行输入M位的数。 100%数据N<=10^9,M<=20,K<=1000 40%数据N<=1000 10%数据N<=6
Output
阿申想知道不出现不吉利数字的号码有多少种,输出模K取余的结果.
Sample Input
111