bzoj1009&&luogu3193 [HNOI2008]GT考试(KMP+DP+矩阵倍增)

17 篇文章 0 订阅
12 篇文章 0 订阅

http://www.elijahqi.win/archives/408
题目描述
阿申准备报名参加GT考试,准考证号为N位数X1X2….Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。他的不吉利数学A1A2…Am(0<=Ai<=9)有M位,不出现是指X1X2…Xn中没有恰好一段等于A1A2…Am. A1和X1可以为0

输入输出格式
输入格式:

第一行输入N,M,K.接下来一行输入M位的数。 N<=10^9,M<=20,K<=1000

输出格式:

阿申想知道不出现不吉利数字的号码有多少种,输出模K取余的结果.

输入输出样例
输入样例#1:

4 3 100
111
输出样例#1:

81
读这题加想题花了很久很久。。看了题解想也仍然想不懂。前几天和小可爱对赌了暑假作业,昨天一问发现我差了好多,于是年轻的我本想2小时搞定这题,但基础太差。中间出去吃个饭就从亮点到现在才搞定,面对作业甚是害怕

题解写的有点乱,慢慢看。

输出模K取余的结果. 一般来说可以判定是用矩阵倍增的方法了

f[i][j]表示在第i号时 后缀中有j位与不吉利的前缀j位相同 的个数有多少

如果仅仅这个定义那么应该是10的(i-j)次方种,但是我们要求前面同时也不能出现任何不吉利的数字 为了保证不重复,所有的f[i][j]都不包含 后缀中长度大于j且与不吉利数前缀的前j个相同的。反例:从1到m标号,不吉利数为123124时,f[i][2]计数的方案包含f[i][5]计数的方案 的情况

再zhx得指引下 先来理解好朴素dp 瞬间感觉自己渣渣 这dp都

    f[0][0]=1;
    for(int i=0;i<=n;i++){
        for(int j=0;j<m;j++){
            for(int ch='0';ch<='9';ch++){
                int k=j;
                while(k>0&&s[k+1]!=ch) k=next[k]//加入新加入的不等于不吉利串中的新枚举的那个数,我们就不断倒kmp制作的Next数组
                if(s[k+1]==ch) k++; 如果匹配成功则++但总存在完全不是不吉利串中的任何一位,那我们直接加就好了
                if(k!=m) f[i+1][k]+=f[i][j];
            }
        }
    }

写不好 想来是题目练习的太少了

按顺序处理准考证号每一位,
设f[i][j]表示:准考证号前i位中 后j位与不吉利数的前j位相同时,前i位的方案数
那么答案ans=f[n][0]+f[n][1]+…+f[n][m-1]

有人可能会想了,我中间任意几位和不吉利数字随便几位相同,那我也可能是答案啊

其实 这在递推的过程中已经解决 每次转移的时候f[i][j]只能由f[i-1][k]得到,相当于填完第i-1位后,将其后缀k(长为k的后缀)后面新添一位num,之后这个i位数的 与不吉利数前缀相同的最长后缀是:后缀j
i>=1时:f[i][j]=f[i-1][0]*a[0][j]+f[i-1][1]*a[1][j]+…+f[i-1][m-1]*a[m-1][j]

列出矩阵构造的答案

自己推导一下next数组配合程序看一下 样例123124 行表示原匹配长度 列表示新加一位匹配数变成列数的方案数

9 1 0 0 0 0
8 1 1 0 0 0
8 1 0 1 0 0
9 0 0 0 1 0
8 1 0 0 0 1
7 1 0 1 0 0

#include<cstdio>
#include<cstring>
#define N 30
struct matrix{
    int f[N][N],l,c;
}base,base1;
int kk,n,m,next[N];
char a[N];
inline matrix multiply(matrix a,matrix b){
    matrix c;c.l=a.l;c.c=b.c;memset(c.f,0,sizeof(c.f));
    for (int i=1;i<=a.l;++i){
        for (int j=1;j<=b.c;++j){
            for (int z=1;z<=a.c;++z){
                (c.f[i][j]+=((long long)a.f[i][z]*b.f[z][j]%kk))%=kk;
            }
        }
    }
    return c;
}
int main(){
    //freopen("3193.in","r",stdin);
    //freopen("3193.out","w",stdout);
    scanf("%d%d%d",&n,&m,&kk);
    scanf("%s",a);
    int i=0,j=-1;next[0]=-1;
    while (i<m){
        if (j==-1||a[i]==a[j]){
            ++j;++i;next[i]=j;
        }else j=next[j];
    }
    for (int i=m;i>=1;--i) a[i]=a[i-1];
    base.c=m;base.l=m;base1.c=m;base1.l=m;base1.f[1][1]=1;
    for (int i=0;i<m;++i){
        for (int j='0';j<='9';++j){
            int k=i;
            while (k&&a[k+1]!=j) k=next[k];
            if (a[k+1]==j) ++k;
            if (k!=m) base.f[i+1][k+1]++; //避免构造由i个匹配变成m个匹配的个数 
        }
    }
/*  for (int i=1;i<=m;++i){
        for (int j=1;j<=m;++j) printf("%d ",base.f[i][j]);printf("\n");
    }*/
    int k=n;
    for (;k!=0;k>>=1,base=multiply(base,base)){
        if (k&1) base1=multiply(base1,base);
    }
    int ans=0;
    for (int i=1;i<=m;++i) (ans+=base1.f[1][i])%=kk;
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值