区间内位数大小不降的个数(数位dp)

题目:https://www.acwing.com/problem/content/1084/
题意:某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
1≤a≤b≤2^31 - 1
题解:数位dp。
首先:要求[l,r]内符合题目限制的数的个数,那么只需要求出[0,r]的个数和 [0,l-1]的个数,两个相互一减,就是[l,r]的个数。
其次:从最高位开始分析,假设最高位是x,如果这一位选择填[0,x-1]中的某个数,那么后面那的那些位就可以随便的填(当然要满足题目的限制条件),也就是图中的左子树(这个怎么求,等会再讲,只需要明白,这里可以经过预处理直接O(1)得到答案即可),右子树就是这一位选择x,继续往下走,这样一直走到最后,所有的左子树的方案数再加上最后一个右节点的方案数(本身是不是),就是最后的答案。
在这里插入图片描述
左子树应该怎么求:数位dp的分析方法基本都是这样,只不过就是左子树怎么求不一样,不同的题不同的做法,得现场分析,如果时间复杂度允许,甚至暴力直接求也可以,毕竟很多题的位数一般不高,所以树的深度也不高。
这个题求左子树,从已知的条件可以知道,现在能用的信息是,总共还有几位需要填,并且最高位填了某一个数,在这俩个限制的情况下,这个位数总共能够填多少种方案,这里就可以用一个dp来欲出来答案,f[i][j]表示最高位是j,一共有i位的方案数。
在这里插入图片描述

#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
typedef unsigned long long LL;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;

const int N = 15;
int f[N][N];  // f[i,j]表示一共有i位,且最高位填j的数的个数
void init() {
    //dp预处理出来,确定最高位后,可以得到的所有方案数
    for (int i = 0; i <= 9; i++) f[1][i] = 1;
    for (int i = 2; i < N; i++)
        for (int j = 0; j <= 9; j++)
            for (int k = j; k <= 9; k++) f[i][j] += f[i - 1][k];
}
int dp(int n) {
    //如果是0的情况,直接返回值,一位下面nums中没有数,可能会出问题
    if (!n) return 1;
    vector<int> nums;
    //获得某一位
    while (n) nums.emplace_back(n % 10), n /= 10;
    int res = 0;  //记录答案
    int last = 0;  //记录上一位是多少
    //从最高位开始枚举
    for (int i = nums.size() - 1; i >= 0; i--) {
        int x = nums[i];
        //枚举这一位能够选择的比x小的数量,即树的左子树的答案
        for (int j = last; j < x; j++) {
            res += f[i + 1][j];
        }
        //如果出现了这一位比上一位下,那么接下来的情况都不符合
        if (x < last) break;
        //往下一位走
        last = x;
        //如果走到了最后一位,也就是0位,没有break的话,那么这个数本身也是
        //所以也要 ++
        if (!i) res++;
    }
    return res;
}
signed main() {
    init();
    int l, r;
    //求[0,r]的所有符合的数,再减去[0,l-1]所以符合的数,就是这个区间符合的数
    while (cin >> l >> r) printf("%d\n", dp(r) - dp(l - 1));
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值