ural1057 Amount of Degrees数位统计入门题

        题目大意:给你一个区间,由[m,n]表示,然后给你一个k,一个b,分别代表在这个区间内的数字的b进制数内含有k个1的数字有几个。

        很明显,此题满足区间减法,就是[m,n]的值就等于[0,m]的值减去[0,n-1]的值,注意是n-1,我因为这个WA了一发,还找了半个小时的错,很奇怪,这个写错了之后数据也不好找,提供一组数据:13974 28320 8 2,answer应该是3148

        在知道有数位统计这个东西之前,我是想用组合数学做的,大体思想就是统计出大数的最高位与小数的最高位之间有几位,然后填坑法求解,只是有个想法,并没有把代码写出来。后来上了数位统计的集训课,就试着用这道题联系一下,其实思路很简单,用一个数组f[i][j]来记录前i位二进制数中含有j个1的有几个,这里有一个递推公式:f[i][j] = f[i-1][j] + f[i-1][j-1],原因很简单,对于第i位,要么为1,要么为0,如果是1,就是f[i-1][j-1],如果是0,就是f[i-1][j]。初始设f[0][0]为1,dp出来就好了

        本题解法,就是计算出来从0到n的二进制数含有k个1的有几个,注意最后要讨论这个数本身是不是,此处用到的算法:x&(1<<i),判断x第i位是不是1;x^(1<<i),让x等于x去掉最高位的1的数。

        最后对于非二进制,只要找到b进制第一个大于1的位置,然后从这个位置往后全部置为1就好了,因为题目要求就是找有几个1,大于1的话就是说明这个数比后面全是1的数大,就可以直接用后面全是1的数代替。

AC代码:

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int f[35][35];
int a[35];
void init()
{
    memset(f,0,sizeof(f));
    f[0][0] = 1;
    for(int i = 1; i <= 31; 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 count(int x, int k){
    int tot = 0;
    int ans = 0;
    for(int i = 31; i > 0; i--)
    {
        if(x&(1<<i))
        {
            tot++;
            if(tot > k) break;
            x = x^(1<<i);
        }
        if((1 << (i - 1)) <= x)
        ans+=f[i-1][k-tot];
    }
    if(tot + x == k) ++ans;   //此时x == 0,答案加上x
    return ans;
}
int main(){
    //freopen("in.txt","r",stdin);
    init();
    int m,n,k,b;
    while(scanf("%d%d%d%d",&m,&n,&k,&b)!=EOF)
    {
        //cout<<count(13974,8)<<endl<<count(28320, 8);
        if(b == 2)                                 //此处写麻烦了,可以整个都用数组存起来的,不用再转化成数字,这样的话函数内部也得改
        printf("%d\n",count(n,k) - count(m-1,k));
        else
        {
            int mm,nn;
            mm = nn =0;
            int tem = 0;
            while(m){
                a[tem++] = m % b;
                m /= b;
            }
            int cn = 1;
            for(int i = tem - 1; i >= 0; i--)
            {
                if(a[i] > 1)
                {
                    for(int j = i; j >= 0; j--)
                    a[j] = 1;
                    break;
                }
            }
            for(int i = 0; i < tem; i++)
            {
                mm+=a[i]*cn;
                cn*=2;
            }


            tem = 0;
            while(n)
            {
                a[tem++] = n %b;
                n /= b;
            }
            cn = 1;
            for(int i = tem - 1; i >= 0; i--)
            {
                if(a[i] > 1)
                {
                    for(int j = i; j >= 0; j--)
                    a[j] = 1;
                    break;
                }
            }
            for(int i = 0; i < tem; i++)
            {
                nn+=a[i]*cn;
                cn*=2;
            }
            printf("%d\n",count(nn,k) - count(mm-1,k));
        }
    }
}
</span>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值