【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

4 3 100
111

Sample Output

81

HINT

 

Source

你可能感兴趣的:(2008)