蓝桥杯-K好数(动态规划)

问题描述
如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。

输入格式
输入包含两个正整数,K和L。

输出格式
输出一个整数,表示答案对1000000007取模后的值。
样例输入
4 2
样例输出
7
数据规模与约定
对于30%的数据,KL <= 106;

对于50%的数据,K <= 16, L <= 10;

对于100%的数据,1 <= K,L <= 100。

题目如上

我写这道题第一反应是用数学方法计算,后来发现是不相邻(而不是不相等),需要分为太多情况,所以用数学的排列组合不适用。

然后我想到用递归,因为最近在学习栈和队列,对递归有了一些理解,我写出如下:

int goodnum(int k,int l,int i,int &n,int flag)   //其中k是进制数,l是数字共有多少位,i是当前所在数字的位数
                                                                         //n用来记录共有多少个符合要求的数字
                                                            //flag用来保存上一位数字,且main函数调用时记录为-1,使第一位不可能为零
{
    for(int h=0;h<k;h++){                             //这个for循环用来测试这一位所有可能数字
        if(h==(flag+1)||h==(flag-1))                       //判定这一位和上一位数字是否符合要求
            continue;
        if(i==l)                   //如果这是最后一位,则n加一(如果不符合要求,则在上一个if就跳出了,不会执行这一句
            ++n;
        else
            goodnum(k,l,i+1,n,h);            //递归部分
    }
    if(n>=1000000007)
        n-=1000000007;
    return n;                                  //函数结束条件
}

这个递归方法代码简洁,但执行速度过慢,导致超时,所以递归方法一般并不适用于计算量很大的情况,因为他几乎会遍历每一种可能

对这道题实在没辙了(我是真的菜,)所以上网找到解题方法,方法解释如下:

动态规划

首先定义一个op[i][j]数组,其中i表示数字位数,j表示该数字第一位,op表示该情况时数字有多少个;
然后对op[1][x]初始化为1;因为数字只有一位时只有一种可能;

该方法是把数字从后一步一步向前推理

然后当数字为两位时,比如op[2][x],现在测定x与op[1][i]数字为一位时每种情况对比,如果x和i的情况符合题目要求,则把op[1][i]的数字加到op[2][x]中,至此遍历op[1][i]中所有情况就可以知道op[2][x]的可能情况,这种操作方法可以理解为所有一位数的可能都计算好了,现在在这些数字前面再放一个x组成两位数,如果符合情况,就是两位数(首位为x)的所有组成可能。然后一步一步向前放一个数一直到组成所要求的位数。最终答案即是op[位数][x],的所有遍历之和,其中x不能为零,一位数字第一位不能为零。
最后,为什么不从前往后推,最终答案不把首位为零的情况加进去呢? 因为如果从前往后推,则之后的每一位都可能会因为前面有把首位为零情况算入而受其影响导致答案错误,而且最终答案也无法把首位为零情况分离出去

最后,代码如下:

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    int K,L;
    int op[102][102];
    long long int sum=0;
    cin>>K>>L;
    memset(op,0,sizeof(op));            //初始化数组,我就是因为这个才又错了一遍
    for(int i=0;i<K;i++){            //初始化
        op[1][i]=1;
    }
    for(int i=2;i<=L;i++){            //每一位的情况
        for(int h=0;h<K;h++){                  //这一位上数字的所有情况
            for(int g=0;g<K;g++){                   //后一位数字所有情况
                if(g!=h+1&&g!=h-1){
                    op[i][h]+=op[i-1][g];            //如果放在前面的h和原第一位的g符合要求,则加入
                    op[i][h]=op[i][h]%1000000007;
                }
            }
        }
    }
    for(int i=1;i<K;i++){
        sum+=op[L][i];
        sum=sum%1000000007;
    }
    cout<<sum<<endl;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值