bzoj1009 [HNOI2008]GT考试

1009: [HNOI2008]GT考试

Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 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

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;
}

 

    
posted @ 2018-02-23 12:03  zbtrs  阅读(187)  评论(0编辑  收藏  举报