bzoj1009 [HNOI2008]GT考试
1009: [HNOI2008]GT考试
Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 4517 Solved: 2799
[Submit][Status][Discuss]
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位的数。 N<=10^9,M<=20,K<=1000
Output
阿申想知道不出现不吉利数字的号码有多少种,输出模K取余的结果.
Sample Input
4 3 100
111
111
Sample Output
81
分析:以前做过一道类似的题:传送门,但是本题的n特别大,不能存到状态里面,怎么办?
如果当前匹配到不吉利数字的号码的第j位,枚举第j+1位的数字,能够转移到的地方是固定的. 构造一个转移矩阵,令a[i][j]表示不吉利数字的第i位跳到第j位有多少种方案. 这个很容易利用kmp算法求出来.那么f[i][j] = f[i-1][0] * a[0][j] + f[i - 1][1] * a[1][j] +......
这是一个矩阵相乘的表达式,可以用矩阵快速幂来优化.
通过矩阵快速幂,可以不用记录n,最后的答案就是矩阵第一行的和.因为第m列是不符合要求的,所以实际上只需要记录矩阵的0~m-1列即可.
这道题的转移方式和那道题有很大的区别. 因为要用到矩阵快速幂不记录n,必须知道f[i][j]从哪些状态转移而来,转移过来的方案数是多少,那么就必须要求出a数组. 相反的,如果直接递推转移,那么很容易就能知道f[i][j]能够转移到哪些状态,直接从f[i][j] 转移到f[i + 1][k]即可.由此可以看出“转移到”和“转移来”有很大的区别,记录的东西也会有所不同.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int n,m,mod,nextt[30],ans; char s[30]; struct node { int x[30][30]; void clear() { memset(x,0,sizeof(x)); } }a,b,c; void operator *= (node &p,node &q) { c.clear(); for (int i = 0; i < m; i++) for (int j = 0; j < m; j++) for (int k = 0; k < m; k++) c.x[i][j] = (c.x[i][j] + p.x[i][k] * q.x[k][j] % mod) % mod; p = c; } void kmp() { int j = 0; for (int i = 2; i <= m; i++) { while (j && s[j + 1] != s[i]) j = nextt[j]; if (s[j + 1] == s[i]) j++; nextt[i] = j; } int k; b.clear(); for (int i = 0; i < m; i++) { for (char j = '0'; j <= '9'; j++) { k = i; while (k && s[k + 1] != j) k = nextt[k]; if (s[k + 1] == j) k++; b.x[i][k]++; } } } void qpow(int p) { a.clear(); for (int i = 0; i < m; i++) a.x[i][i] = 1; while (p) { if (p & 1) a *= b; b *= b; p >>= 1; } } int main() { scanf("%d%d%d",&n,&m,&mod); scanf("%s",s + 1); kmp(); qpow(n); for (int i = 0; i < m; i++) ans = (ans + a.x[0][i]) % mod; printf("%d\n",ans); return 0; }