AcWing 1081:度的数量 ← 数位DP

【题目来源】
https://www.acwing.com/problem/content/1083/

【题目描述】
求给定区间 [X, Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:
17=2^4+2^0
18=2^4+2^1
20=2^4+2^2

【输入格式】
第一行包含两个整数 X 和 Y,接下来两行包含整数 K 和 B。

【输出格式】
只包含一个整数,表示满足条件的数的个数。

【输入样例】
15 20
2
2

【输出样例】
3

【数据范围】
1≤X≤Y≤2^31−1,
1≤K≤20,
2≤B≤10

【算法分析】
● 数位DP(Digit Dynamic Programming)是一种用于解决数字数位相关计数问题的动态规划算法。其核心思想是将数字按位拆解,通过递归或递推的方式处理每一位的选择,并利用记忆化搜索来避免重复计算,从而高效统计满足特定条件的数字数量。

●​​​​​​​ 数位DP通过记录前导零、数位限制等状态,将问题复杂度从 O(n) 降低到 O(log n),能够处理非常大的数字范围(如 10^18)。其实现通常是将统计 [le, ri] 的问题转化为统计 [1, ri] 和 [1, le-1] 的结果相减

● 组合数的性质:C_{i}^{j}=C_{i-1}^{j-1}+C_{i-1}^{j} ,对应的 LaTex 代码为:C_{i}^{j}=C_{i-1}^{j-1}+C_{i-1}^{j}

●​​​​​​​ 本题算法思想(改编自:https://www.bilibili.com/video/BV1Ff4y1e7YW
(一)f[i][j] 表示在 i 个位置上放置 j 个 1 的组合数
(二)利用短除法将数字 n 对 B 求余所得的各位依次放入一个名为 v 的 vector 中。然后,从右至左枚举 v。假设枚举到第 i 位,第 i 位上的数字是 x,分以下儿种情况讨论(其中,pre 记录第 i 位之右放置 1 的个数)。
1. x==0,直接跳过,继续向左枚举一位;
2. x==1,第 i 位分成两种情况:
(1)第 i 位放 0,左边 i-1 个位上可以放 k-pre 个 1,cnt+=f[i-1][k-pre];
(2)第 i 位放 1,左边 i-1 个位上的情况不能用组合数计算,因为要保证答案中的数字比原数字小,所以固定第 i 位为 1,继续向左枚举一位;
3. x>1,第 i 位分成三种情况:
(1)第 i 位放 0,左边 i-1 个位上可以放 k-pre 个 1,cnt+=f[i-1][k-pre];
(2)第 i 位放 1,左边 i-1 个位上可以放 k-pre-1 个 1,cnt+=f[i-1][k-pre-1];
(3)第 i 位放大于 1 的数,已经不合要求,没必要继续枚举,直接 break。

【算法代码】

#include<bits/stdc++.h>
using namespace std;

const int N=35; //数的位数
int f[N][N]; //f[i][j]表示在i个位置上放置j个1的组合数
int k,b;

void init() {
    for(int i=0; i<N; i++)
        for(int j=0; j<=i; j++)
            if(j==0) f[i][j]=1;
            else f[i][j]=f[i-1][j-1]+f[i-1][j];
}

int dp(int n) {
    if(n==0) return 0;
    vector<int> v;
    while(n) {
        v.push_back(n%b);
        n/=b;
    }

    int cnt=0,pre=0;
    for(int i=v.size()-1; i>=0; i--) {
        int x=v[i];
        if(x>0) {
            cnt+=f[i][k-pre];
            if(x>1) {
                if(k-pre-1>=0) cnt+=f[i][k-pre-1];
                break;
            } else {
                pre++;
                if(pre>k) break;
            }
        }
        if(i==0 && pre==k) cnt++;
    }
    return cnt;
}

int main() {
    init();
    int le,ri;
    cin>>le>>ri>>k>>b;
    cout<<dp(ri)-dp(le-1)<<endl;

    return 0;
}

/*
in:
15 20
2
2

out:
3
*/





【参考文献】
https://www.bilibili.com/video/BV1Ff4y1e7YW/
https://www.acwing.com/solution/content/34003/
https://mp.weixin.qq.com/s/iGLhLr0V-S6mTNa5-9GGYA
https://blog.csdn.net/hnjzsyjyj/article/details/108510184
https://www.acwing.com/problem/content/3713/
https://www.cnblogs.com/wc529065/p/18729652
https://www.nowcoder.com/discuss/701964924284588032
https://mp.weixin.qq.com/s/A3aq40aKEt5U62ft8N-Mxg


posted @ 2025-12-16 23:53  Triwa  阅读(3)  评论(0)    收藏  举报