数位类统计问题ural 1057

题目大意

求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K个互不相等的B的整数次幂之和。例如,设X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:
17=24+20
18=24+21
20=24+22
输入:第一行包含两个整数X 和Y。接下来两行包含整数K 和B。
输出:只包含一个整数,表示满足条件的数的个数。
数据规模: 1XY23111K202B10

解题思路

所求的数为互不相等的幂之和,亦即其B进制表示的各位数字都只能是0和1。我们只需讨论二进制的情况,然后其他进制可以转换成二进制来求解。
首先注意到本题具备区间减法性,即count(i…j) = count(0…j)-count(0…i-1),那么接下来的研究重点就是如何求0~n之间有多少符合要求的数。
假设n有i位,它的二进制形式是 (ai1ai2...a1a0)2 。我们从高位向低位扫描它,当某一位 aj 是0时则继续向下看,当 aj 为1时计数。计数方法如下:

  • 遍历过程中要维护一个值tot,记录目前为止遍历到几个1
  • 看到 aj 为1后,给ans累加上 (ktotj) 。它的意义是:把 aj 置0,然后右边j位选k-tot个变为1,这样的数显然满足“小于等于n且含k个1”。最后把tot++。

剩下的工作就是预处理组合数F(n,m)了,二进制的情形讨论完毕。接下来讨论其他进制与二进制的转换。
其他进制与二进制的一个明显区别是该进制下的X和Y某一位可能大于1,我们必须把它变成全01的形式,然后套用二进制情形的解法。

  • 转换方法一
    把X转化为“大于等于X且B进制下每位都是0或1且最接近X的数”,把Y转化为“小于等于Y且B进制下每位都是0或1且最接近Y的数”。设它们分别是nx和ny,如果我们已经实现了二进制情形的统计函数f(x,y,k),那么f(nx,ny,k)就是B进制下的答案。
  • 转换方法二
    把X和Y都转化为“小于等于它且B进制下每位都是0或1且最接近它的数”,这样只需要写一个转化函数。不过这种情况下求得f(nx,ny,k)后要特判一种情况:如果 (X)B 含大于1的位而 (nx)2 又恰好含k个1,这时候答案会多一,因此要把ans-1。

代码

#include <cstdio>

int F[33][33];

void init()
{
    F[0][0] = 1;
    for(int i=1; i<=30; i++)
    {
        F[i][0] = F[i-1][0];
        for(int j=1; j<=i; j++)
            F[i][j] = F[i-1][j] + F[i-1][j-1];
    }
}

int calculate(int n, int k)
{
    int ans = 0, tot = 0;
    if(n == 0) return 0;

    for(int i=30; i>=0; i--)
    {
        if(n & (1<<i))
        {
            ans += F[i][k-tot];
            tot ++;
        }
        if(tot > k) break;
    }
    if(tot == k) ans ++;
    return ans;
}

// 求B进制形式下,所有小于n且所有位都为0或1中的最大数
int change(int n, int B)
{
    int num[33], top = -1;
    int p, ans;
    while(n)
    {
        num[++top] = n % B;
        n /= B;
    }
    // 在n的B进制串中从高到低找到第一个不为0、1的位
    // 把它置为1,然后后面全为1
    for(p=top; p>=0 && num[p]<=1; p--);
    ans = 0;
    for(int j=top; j>p; j--)
        if(num[j])
            ans += (1<<j);
    if(p >= 0)
        ans += (1<<(p+1))-1;
    return ans;
}

bool special(int x, int nx, int B, int k)
{
    /// nx = change(x, B)
    bool flag = true; // x的B进制串中只含01
    while(x)
    {
        if(x % B > 1)
        {
            flag = false;
            break;
        }
        x /= B;
    }
    int tot = 0; // 统计x二进制串中1的个数
    while(nx)
    {
        if(nx & 1) tot ++;
        nx >>= 1;
    }
    if(tot == k && !flag)
        return true;
    return false;
}

int main()
{
    int x,y,k,B, ans;
    init(); // 预处理组合数
    while(scanf("%d%d", &x,&y) != EOF)
    {
        scanf("%d%d", &k,&B);
        if(B > 2)
        {
            int nx = change(x, B);
            int ny = change(y, B);
            ans = calculate(ny, k) - calculate(nx-1, k);

            // 特判一种情况
            if(special(x, nx, B, k))
                ans --;
        }
        else ans = calculate(y, k) - calculate(x-1, k);
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值