You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.9 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 1081 度的数量

一、题目描述

求给定区间 [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

输入格式 第一行包含两个整数 XY,接下来两行包含整数 KB

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

数据范围 1≤X≤Y≤2^{31}1,1≤K≤20,2≤B≤10

输入样例

15 20
2
2

输出样例

3

二、数位DP知识

数位dp的题目一般会问 : 指定区间内,满足某种性质的数的个数

解题技巧

  • 利用 前缀和,比如求区间[x,y]中的 个数,转化成求[0,y]个数 -[0,x-1]个数

三、解题思路

题目就是在一个区间[l,r]内,找出有多少个符合题意的数,这里的符合题意是指:这个数的 b进制 表示中,其中有k个位置上是1、其他位上全是0

比如样例中B=2K=2,这个数是18.就是把18化为二进制18 化为二进制数为:10010,其中有21,其他位都是0

再比如 B=3K=2,这个数是12.就是把12化为三进制12化为三进制为110 ,其中也有21,其余都是0.

暴力法

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;

int l, r;     // 数位DP的区间范围
int k, b;     // k:个进制位为1,b进制
int a[N], al; // 用来分解b进制的数组

// 将x转化为b进制
void calc(int x, int b) {
    al = 0;
    memset(a, 0, sizeof a);
    while (x) a[++al] = x % b, x /= b;
}

/*
通过了 7/14个数据
一半的分数也是OIer的梦想
*/
int res;
int main() {
    cin >> l >> r >> k >> b;
    // 1、将[l,r]之间所有数字进行b进制分解
    for (int i = l; i <= r; i++) {
        calc(i, b);

        int cnt = 0;
        bool bad = false; // 是不是全是0或1一旦是bad=true,表示出现了大于数字1的数位此时一票否决~

        // 2、检查是不是存在大于1的大于1的是无效数字跳过
        for (int j = 1; j <= al; j++) {
            if (a[j] > 1) {
                bad = true;
                break;
            }
            // 3、统计一下1出现的次数
            if (a[j] == 1) cnt++;
        }
        // 4、判断出现的次数是不是等于k,如果是,那么找到了一个答案,计数
        if (!bad && cnt == k) res++;
    }
    printf("%d\n", res);
    return 0;
}

五、dfs数位dp模板

数位dp总结之从入门到模板

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

const int N = 32; // 输入的数据范围2^31-1,也就是整数上界。2进制是最小的进制32也够了

int l, r;     // 数位DP的区间范围
int k, b;     // k:个进制位为1,b进制
int a[N], al; // 用来分解b进制的数组
int f[N][N];  // f[i][j] i:枚举到的位置 j:已经计算完1的个数,这里需要一点DP的状态描述的思想只有一维记录到哪了是无法描述整个当时场景的

/**
 *
 * @param u 当前枚举到的数位
 * @param st  数字1出现的次数
 * @param op  是否贴上界
 * @return  在当前情况下,它 后续 符合条件的一共有多少个数
 */
int dfs(int u, int st, bool op) {
    // 到最后一位数位是否恰好有k个不同的1,是:贡献价值+1贡献价值+0
    if (u == 0) return st == k;

    if (op == 0 && ~f[u][st]) return f[u][st]; // 不贴上界,并且计算过,返回结果

    /*
    ① 不贴着上界,可以将此区间算满,缓存起来,不用重复计算
    ② 贴上界,每次重新计算

    本题要求:
    (1)如果op = 1, 贴上界
        如果a[u]=0,那么up最大是0,否则,up最大是1,简写成min(a[u],1)
    (2)如果op = 0, 不贴上界,可以选择的范围[0,1],up最大是1
    */
    int ans = 0;
    int up = op ? min(a[u], 1) : 1; // up就是你的极限本题这句是一个精华点

    for (int i = 0; i <= up; i++) { // 枚举上限之内的数字(其实也只有0和1有机会这还要看up的取值。up=0,跑一次循环up=1,跑两次循环)
        // 如果i==0,则st+i=st,st不会变大也不会超过k,此句无影响后面的代码执行。
        // 如果i==1,则表示多选择了一个是1的数位st+1,有可能已经超过了k的限制再向后跑深搜就没有了意义及时止损剪枝
        if (st + i > k) continue; // 剪枝更健康

        // u-1:继续向低一位进行前进
        // st+i:1的个数变化情况如果i==0,则表示u这个数位没有选择数字1如果i==1则表示u这个数位选择了数字1不断的向后传递这个信息用于判断是不是符合条件的数字
        // op && i == a[u] :此参数的含义是是否贴合上限
        // op:原来是贴上界的 &&   i == a[u]: 当前位取值也是贴上界
        // 那么,下面一位是继承前面的结果关系,也就是,它也在贴上界的情况下进行讨论

        // ==的运算优先级 高于 && 所以先判断 i == a[u]是第一步,与 op 相 && 是第二步
        ans += dfs(u - 1, st + i, op && i == a[u]);
    }
    // 只记录不受上限限制的个数,受上限限制的每次重新计算
    // 为什么只记录不贴上界的结果,贴上界的为什么不能直接使用结果?
    // 这是因为:如果第一位贴了上界,那么第二位也可能继续贴上界,导致后续的无法取完整,无法直接使用结果
    if (!op) f[u][st] = ans;
    /*这里对应上面的记忆化在一定条件下时记录保证一致性当然如果约束条件不需要考虑lead这里就是lead就完全不用考虑了*/
    return ans;
}

// 采用类似于前缀和的思想所以需要封装成一个calc函数,计算两次,然后取差值
int calc(int x) {
    al = 0;                            // 临时数组a初始化这里挺有意思把al=0就相当于全新初始化掉了a,不用采用memset(a,0,sizeof a);
    while (x) a[++al] = x % b, x /= b; // 把x按照进制分解到数组中,下标从1开始
    return dfs(al, 0, true);           // 从最高位开始目前数字1出现了0次贴上界
}

int main() {
    memset(f, -1, sizeof f); // 初始化
    cin >> l >> r >> k >> b; // 边界k个数位是1b进制
    // 前缀和
    cout << calc(r) - calc(l - 1) << endl;
    return 0;
}