数字游戏

数字游戏

题目描述

在这里插入图片描述


核心思路

在这里插入图片描述

对于一个整数n,我们把它的每一位都抠出来,从最高位开始考虑:

假设原数n中第i位的数为x,它上一位的数为j,那么当我们枚举模拟时,第i位可以分为两个分支,左边分支可以填0到x-1中的某个数,右边分支是填x这个数。

  • 考虑左分支的方案数:假设我们从0到x-1中选出了某个数k( j ≤ k < x , 保 证 非 下 降 ) j\leq k<x,保证非下降) jk<x),填到了第i位,则方案为 f [ i ] [ k ] f[i][k] f[i][k]表示一共有i位,且最高位为k的方案数。那么左分支的方案数为 ∑ j = l a s t x − 1 f [ i ] [ k ] \sum \limits _{j=last}^{x-1}f[i][k] j=lastx1f[i][k]
  • 考虑右分支的方案数:如果 x < l a s t x<last x<last,无法满足非下降条件, 直接break, 否则填x然后从右分支又开辟出左右分支,继续判断下一位的情况
  • 如果i == 0, 可以填任意数,也是一种方案, res++

在这里插入图片描述

在这里插入图片描述


代码

写法1

#include<iostream>
#include<vector>
using namespace std;
const int N=15;
int f[N][N];///f[i][j]表示最高位是j并且一共有i位的不降数的方案数
//预处理出最高位是j并且一共有N位的不降数的方案数
void init()
{
    //如果只有1位,那么这个数肯定是满足不下降性质的,即对于只有个位的话,那么0到9这十个个位数字都是合法的方案,且方案数是1
    for(int j=0;j<=9;j++)
    f[1][j]=1;
    //从有2位一直枚举到有N位
    for(int i=2;i<N;i++)
    {
        for(int j=0;j<=9;j++)//枚举当前位的最高数可以填啥
        {
            //k是j的下一位,j是k的上一位,由于j<=k才能满足不下降的性质,因此k应该从j开始取
            for(int k=j;k<=9;k++)
            {
                f[i][j]+=f[i-1][k];
            }
        }
    }
}
int func(int n)
{
    //n=0,0满足不下降性质,是一个合法的方案
    if(!n)
    return 1;
    vector<int>nums;
    while(n)
    {
        nums.push_back(n%10);
        n/=10;
    }
    int res=0;//记录方案数
    int last=0;//记录上一位的数是多少
    //从高位向低位枚举
    for(int i=nums.size()-1;i>=0;i--)
    {
        int x=nums[i];//取出第i位上的数
        //要保证比下一位>=上一位,所以从last开始枚举,最多枚举到x-1,
        //last为上一位的数,对下一位的枚举是有限制的
        //这里不能写成res+=f[i][j],因为比如原数n=1934,存入num向量中就是4391对应下标就是0123
        //如果写成f[i][j],那么当下标i=3时,求的就是f[3][j]含义就是最高位是j并且一共有3位的方案数
        //但实际是一共有4位,因此是f[i+1][j]而不是f[i][j]
        for(int j=last;j<x;j++)//计算左边的分支
        {
            res+=f[i+1][j];///左端的节点有i+1个位数(因为第一位的下标是0)
        }
        //如果下一位的数x比上一位的数last还要大,则不满足不下降性质,提前退出
         //特判一下: 如果当前数比前一个数小,不能进入右边的分支
        if(x<last)
        break;
        //说明下一位的数x>=last,那么就更新last
        //进入右边的分支
        last=x;
        //如果能顺利到最后一个数说明,树的最右边这一段的每一个数都是小于等于前一位数的,因而++
        if(!i)//最后再加上最右边末端的分支
        res++;
    }
    return res;
}
int main()
{
    init();
    int l,r;
    while(cin>>l>>r)
    {
        cout <<func(r)-func(l-1)<<endl;
    }
    return 0;
}

写法2

#include<iostream>
using namespace std;
const int N=12;
int a[N];//把整数的每一位数字都抠出来,存入a数组中
int f[N][N];//f[i][j]表示一共有i位,且最高位数字是j的不降数的个数
//预处理不降数的个数
void init()
{
    for(int j=0;j<=9;j++)
    f[1][j]=1;
    for(int i=2;i<N;i++)
    {
        for(int j=0;j<=9;j++)
        {
            for(int k=j;k<=9;k++)
            {
                if(k>=j)
                f[i][j]+=f[i-1][k];
            }
        }
    }
}
int func(int n)
{
    //特判,如果n==0,则返回1
    if(!n)
    return 1;
    int cnt=0;//数组a中的元素个数
    while(n)
    {
        a[++cnt]=n%10;
        n/=10;
    }
    int res=0;//答案
    int last=0;//上一位的数字
    //从高位枚举到低位
    for(int i=cnt;i>=1;i--)
    {
        int now=a[i];//取出当前位的数字
        //处理左分支,可选的数是[0,x-1]
        for(int j=last;j<now;j++)//枚举当前位可以填入的数字
        {
            res+=f[i][j];//累加方案数
        }
        //处理右分支
        if(now<last)//剪枝
        break;
        last=now;//更新last
        //如果走到了最右下角,则说明n这个数它自身也是满足的
        if(i==1)
        res++;
    }
    return res;
}
int main()
{
    init();
    int l,r;
    while(cin >>l>>r)
    cout <<func(r)-func(l-1)<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值