度的数量~

//举例样例即为寻找15到20中所有数的二进制表示中有两个数位为1其余数位为0的数的个数
//结论:寻找两个数之间所有数的b进制表示中,有k个数位为1,其余全部为零的数的个数
//样例中因为2的整数幂的二进制一定只有一个数位为1其余全部为零
//连个不同的2的整数次幂的二进制表示中的1的位置不可能会出现在同一个位置
#include <bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define ll long long
#define Mirai ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#define COUT cout << "###" << endl;
#define pb push_back
using namespace std;
typedef pair<int, int> PII;
using namespace std;
const int N = 35;
int l, r, k, b, a[N], len, dp[N][N];
//dp[i][j]存储的是在枚举第i位时没有限制并且已经选了j个1的前提条件下有多少种方案
int dfs(int pos, int cnt, int limit)
//pos代表当前枚举到哪一位数
//cnt代表在此之前选了多少个1
//limit用于判断当前枚举有无限制
//第一次进入dfs时limit要初始化为1,因为第一位是有限制的,并不能从进制最小数枚举到B进制最大数
{
    if (!pos)//数组下标为0的地方没有储存数值,此处仅用来当作边界条件
    //此时cnt==k则说明已经选了k个1,则结果合法,算作一种情况返回1
        return cnt == k;
    if (!limit && dp[pos][cnt] != -1)//如果当前枚举没有限制
    //并且此状态已经有结果,则直接返回(记忆化搜索返回已·有结果)
        return dp[pos][cnt];
    int res = 0, up = limit ? a[pos] : b - 1;
    //如果有限制,limit为1,up上界取当前数位值
    //如果无限制,limit为0,up上界取进制数减一(当前进制下的最大数位值)
    for (int i = 0; i <= up; i++)//枚举每一个可以枚举到的数位值
    {
        if ((i == 1 && cnt == k) || i > 1)//此处为不合法条件
        //如果当前位置数取到1并且前面取的1已经达到了最大数量,则不需要再取1
        //根据题意可知每一数位上的值只需要枚举0和1两个数,当i大于1时continue
            continue;
        res += dfs(pos - 1, cnt + (i == 1), limit && i == up);
        //最终结果加上枚举下一数位的结果
        //pos-1代表枚举下一位
        //cnt+(i==1)代表如果当前数取1则i==1结果为1,在枚举下一位的时候cnt+1
        //如果i!=1,则i==1结果为零,枚举下一位的时候cnt不需要加1
        //如果当前枚举时是有限制的,而当i取到了最高位,即i==up,那么下一次搜索也必定有限制
        //如果当前有限制,limit=1,而当前i没有取到最高位,则下一次搜索没有限制
        //如果当前没有限制,则下一位也必定没有限制
    }
    //cout<<pos<<" "<<cnt<<" "<<limit<<" "<<up<<" "<<res<<endl;此行输出用来看每一次递归的结果便于理解代码
    return limit ? res : dp[pos][cnt] = res;
    //根据dp数组的意义可知,当limit为0即当前枚举pos位时没有限制时
    //使dp[pos][cnt]=res并返回,使得下一次遍历到此种情况时可以复用当前结果(记忆化搜索保存结果)
}
int cal(int x)
{
    memset(dp, -1, sizeof dp);
    len = 0;//len代表数位长度
    while (x)
        a[++len] = x % b, x /= b;//a中反向存储x在b进制下每一位的数值
    return dfs(len, 0, 1);
}
signed main()
{
    cin >> l >> r >> k >> b;
    //cout<<cal(r)<<endl;
    cout << cal(r) - cal(l - 1) << endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值