zoukankan      html  css  js  c++  java
  • bzoj1009 [HNOI2008]GT考试(AC+矩乘优化dp)

    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

    分析:
    第一眼:这难道不是一道数位dp吗
    f[i][j][k][0/1][0/1][0/1][0/1]
    第i位,添的数字是j,和模式串的第k位相不相同[0/1],模式串的1~k-1位是不是都匹配上了,1~i位中有没有模式串,卡不卡上界

    一看数据范围,数位dp就等着吔shi吧。。。
    所以这又是一道dp
    暴力dp的话,和文本生成器一样

    在构建AC自动机的时候,
    有一个语句是普通的AC自动机不具有的

    for (i=0;i<=9;i++)
        if (!ch[0][i]) ch[0][i]=++tot;  

    这样在之后是有用的

    N的范围使我们不得不考虑优化,因为只有一个字符串
    每个状态都是由固定的几个转移而来的,这就让我们想到了矩阵优化

    那我们就要来构造矩阵了
    首先明确状态转移方程:
    f[i][j] 表示匹配到i位置,在AC自动机上的j节点
    f[i][j]向i+1转移,枚举0~9,如果下一个节点不是ed节点
    那么我们就可以继续转移

    现在我们就要构造一个tot(AC自动机上的节点数量)*tot的矩阵
    H[当前点][可以转移点]=1
    这时AC自动机中看似多余的语句,就起了作用了:
    任何不存在于M中的数字都可以看作是根节点
    这里写图片描述

    说实话,f[1]的初始化我是看不大懂的
    最后的答案实际上就是在H矩阵自乘完之后,再乘上一个f[1]
    把值都加起来

    for (int i=1;i<=tot;i++)
    {
        t[i]=0;
        for (int j=1;j<=tot;j++)
            t[i]=(t[i]+f[1][j]*ans.H[j][i]%p)%p;
        sum=(sum+t[i])%p;
    }

    这里写图片描述

    tip

    在KSM的时候,只用自乘n-1次,因为f[1]我们已经处理出来了

    所有需要循环AC自动机节点的地方,都是从1开始
    除了在处理f[1]的时候:
    这里写图片描述

    //这里写代码片
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define ll long long
    
    char s[30];
    int q[1010],tou,wei,fail[1010],ch[300][30],tot=0,f[2][1001];
    bool ed[1010];
    int p,n,m,k;
    struct node{
        int H[100][100];
        node operator *(const node &a)const
        {
            node ans;
            for (int i=1;i<=tot;i++)
                for (int j=1;j<=tot;j++)
                {
                    ans.H[i][j]=0;
                    for (int k=1;k<=tot;k++)
                        ans.H[i][j]=(ans.H[i][j]+H[i][k]*a.H[k][j]%p)%p;
                }
            return ans;           
        }
        void print(node ans)
        {
            for (int i=1;i<=tot;i++)
            {
                for (int j=1;j<=tot;j++)
                    printf("%d ",ans.H[i][j]);
                printf("
    ");
            }
        } 
        void clear()
        {
            memset(H,0,sizeof(H));
        }
        node KSM(int pp)
        {
            node a=(*this),an=(*this);
            pp--;
            while (pp)
            {
                if (pp&1) 
                   an=an*a;
                a=a*a;
                pp>>=1;
            }
            return an;
        }
    };
    node H,ans;
    
    void insert()
    {
        int i,now=0;
        for (i=0;i<strlen(s);i++)
        {
            int x=s[i]-'0';
            if (!ch[now][x]) ch[now][x]=++tot;
            now=ch[now][x];
        }
        ed[now]=1;
        for (i=0;i<=9;i++)
            if (!ch[0][i]) ch[0][i]=++tot;   
    }
    
    void makefail()
    {
        tou=wei=0;
        for (int i=0;i<=9;i++)
            if (ch[0][i])
                q[++wei]=ch[0][i];
        do
        {
            int r=q[++tou];
            for (int i=0;i<=9;i++)
            {
                if (!ch[r][i])
                {
                    ch[r][i]=ch[fail[r]][i];
                    continue;
                }
                fail[ch[r][i]]=ch[fail[r]][i];
                ed[ch[r][i]]|=ed[fail[ch[r][i]]];
                q[++wei]=ch[r][i];
            }
        }
        while (tou<wei);
    }
    
    void build()
    {
        H.clear();
        for (int i=1;i<=tot;i++)
            if (!ed[i])   //不是结束节点 
            for (int j=0;j<=9;j++)
            {
                if (ed[ch[i][j]]) continue; 
                H.H[i][ch[i][j]]=1;
            }
    }
    
    int main()
    {
        scanf("%d%d%d",&n,&m,&p);
        scanf("%s",s);
        insert();
        makefail();
        build();
        ans=H.KSM(n-1);
        for (int i=0;i<=tot;i++)
        {
            if (ed[i]) continue;
            for (int j=0;j<=9;j++)
                f[1][ch[i][j]]+=(i==0);
        }
        int t[100];
        int sum=0;
        for (int i=1;i<=tot;i++)
        {
            t[i]=0;
            for (int j=1;j<=tot;j++)
                t[i]=(t[i]+f[1][j]*ans.H[j][i]%p)%p;
            sum=(sum+t[i])%p;
        }
        printf("%d",sum%p);
        return 0;
    }
  • 相关阅读:
    TopCoder SRM502 Div1 500 贪心 01背包
    TopCoder SRM502 Div1 1000 动态规划
    LOJ#6433. 「PKUSC2018」最大前缀和 状压dp
    Codeforces 830D Singer House 动态规划
    Codeforces 830C Bamboo Partition 其他
    UOJ#275. 【清华集训2016】组合数问题 数位dp
    Codeforces 806 D. Perishable Roads Dijkstra
    UOJ#53. 【UR #4】追击圣诞老人 树链剖分 k短路
    Java第二天——标识符命名规则、Java的知识、快捷键的使用、Scanner获取值的常用方法
    Scanner的例子
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673128.html
Copyright © 2011-2022 走看看