• BZOJ 1009&洛谷P3193-GT考试【HNOI2008】DP+KMP+矩阵快速幂


    Time Limit: 1 Sec  Memory Limit: 162 MB

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1009

    洛谷:https://www.luogu.com.cn/problem/P3193

    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

    emmmm。。。这题,不会。。。据说是矩阵加速DP配个KMP。。。
    设$f[i][j]$为长串的第i个位置匹配短串的第j个为置,那么最终答案就是$ans=sum_{j=0}^{m-1}f[n][j]$
     
    接下来就是状态转移方程了,由$f[i][j]->f[i+1][k]$那么k是不确定的,有可能失配,那么就变成$f[i+1][0]$了也有可能刚好是下一个即$f[i+1][j+1]$,当然也有可能是j之后失配掉落下来的。所以状态转移方程就出来了: 
     $f[i][j]=sum_{k=0}^{9}f[i-1][p]$ 表示为在短串中对于数字k失配到短串第j位的数量总和。
     
    那么怎么求数字k失配到短串中第j位的数量呢?其实讲到失配的话我们很容易想到KMP算法的next数组,求出next数组后我们对每一个数字(即0到9走一遍):
    get_fail(m,s);
    for (int i=0; i<m; i++)
        for (char ch='0'; ch<='9'; ch++){
            int j=i;
            while (j && s[j+1]!=ch) j=nx[j];
            if (s[j+1]==ch) j++;
            g[i][j]++;//从第i位跳到第j位有多少个数字
        }
     
    把方程整理一下得:$f[i][j]=sum_{k=0}^{m-1}f[i-1][k]*g[k][j]  $
     
    然后考虑一下n实在太大了,即使是线性的复杂度也过不了,所以对这种有点递推意味的式子我们考虑用矩阵快速幂加速一下
    我们认真观察一下就好发现,$f[0][0]=1$,我们将第二维压缩合并一下以F[i]代替,g[][]由于是固定的,我们用G代替,那么根据上式可得$F[1]=G,F[2]=F[1] imes G...$
    即:$F[n]=G^{n}$
    然后跑个矩阵快速幂,将最终答案乘以$F[0]$那么最终的答案就是:$ans=sum_{i=0}^{m-1}f[][i]$
     
    以下是没有加速的代码(洛谷上40分):
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int mac=1e6+10;
    
    char s[50];
    int nx[50],g[50][50],f[mac][30];
    
    void get_fail(int n,char *s)
    {
        int j=0;
        for (int i=2; i<n; i++){
            while (j && s[j+1]!=s[i]) j=nx[j];
            if (s[j+1]==s[i]) j++;
            nx[i]=j;
        }
    }
    
    int main()
    {
        int n,m,mod;
        scanf ("%d%d%d",&n,&m,&mod);
        scanf ("%s",s+1);
        get_fail(m,s);
        for (int i=0; i<m; i++)
            for (char ch='0'; ch<='9'; ch++){
                int j=i;
                while (j && s[j+1]!=ch) j=nx[j];
                if (s[j+1]==ch) j++;
                g[i][j]++;//从第i位跳到第j位有多少个数字
            }
        f[0][0]=1;
        for (int i=1; i<=n; i++)
            for (int j=0; j<m; j++)
                for (int k=0; k<m; k++)
                    f[i][j]=(f[i][j]+f[i-1][k]*g[k][j]%mod)%mod;
                    //f[i][j]表示第i位匹配到第j位的短串
        int ans=0;
        for (int i=0; i<m; i++) ans=(ans+f[n][i])%mod;
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    以下是AC代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int mac=1e6+10;
    
    char s[50];
    int nx[50],m;
    struct Mat
    {
        int a[30][30];
        Mat(){memset(a,0,sizeof a);}
    }g,f;
    
    void get_fail(int n,char *s)
    {
        int j=0;
        for (int i=2; i<n; i++){
            while (j && s[j+1]!=s[i]) j=nx[j];
            if (s[j+1]==s[i]) j++;
            nx[i]=j;
        }
    }
    
    Mat multi(Mat a,Mat b,int mod)
    {
        Mat ans;
        for (int i=0; i<m; i++)
            for (int j=0; j<m; j++)
                for (int k=0; k<m; k++){
                    ans.a[i][j]=(ans.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
                }
        return ans;
    }
    
    Mat qick(Mat g,int n,int mod)
    {
        Mat ans;
        for (int i=0; i<=m; i++) ans.a[i][i]=1;
        while (n){
            if (n&1) ans=multi(ans,g,mod);
            g=multi(g,g,mod);
            n>>=1;
        }
        return ans;
    }
    
    int main()
    {
        int n,mod;
        scanf ("%d%d%d",&n,&m,&mod);
        scanf ("%s",s+1);
        get_fail(m,s);
        for (int i=0; i<m; i++)
            for (char ch='0'; ch<='9'; ch++){
                int j=i;
                while (j && s[j+1]!=ch) j=nx[j];
                if (s[j+1]==ch) j++;
                g.a[i][j]++;
            }
        Mat ans=qick(g,n,mod);
        int sum=0;
        f.a[0][0]=1;
        f=multi(f,ans,mod);
        for (int i=0; i<m; i++)
            sum=(sum+f.a[0][i])%mod;
        printf("%d
    ", sum);
        return 0;
    }
     
     
    路漫漫兮
  • 相关阅读:
    ubuntu分屏终端
    Xcode-5.1.1更改文件盯作者
    Swift——(两)Swift访问元组
    Android Loader使用,屏幕解锁,重复荷载
    医疗信息季节:第二十三届中国国际医疗仪器设备展览会暨研讨会 思考
    UIBarButtonItem 小记边
    L轻松学习inux教程5 知识与学习bash
    Android 教你打造炫酷的ViewPagerIndicator 不仅仅是高仿MIUI
    Android Context 上下文 你必须知道的一切
    Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/12201160.html
Copyright © 2020-2023  润新知