nefu1035数位统计2015哈工程校赛【数位dp入门】

description

上次哈工程校赛有一个比较有趣的问题,我们一起来探讨一下!
给一个整数10,把10用二进制表示为 1010,那么10的二进制表示中有2个1
那么现在的问题是这样的:给一个整数n,然后问小于n的所有数中有多少个数它们的二进制表示中有k个1。
    快敲代码,动作!   

input

 多组输入,每组两个整数n,k(n<;1000000000,k<20)

output

  输出结果

sample_input

10 2
5 1

sample_output

4
3

个人认为这个题更适合作为数位dp的入门题,毕竟只有2进制==说来惭愧,这都是10个月之前的题了,再过俩月大一的都会了然而自己现在刚刚入门。

先说数位dp是什么鬼:

用一个f[i][j]数组表示化成二进制共i位时恰好有j个1.那么显而易见f[i][j]=f[i-1][j]+f[i-1][j-1]。说是显而易见,我也想了好久,比较好解释的方法是把二进制画成树,从论文中盗一张图

左子树根节点没有1,所以方法数是f[i-1][j];右子树根节点是1,所以方法数是f[i-1][j-1]

做法:按照《浅谈数位类统计问题》的做法,首先杨辉三角求组合数预处理这是没得说的,将这个待求的数化成二进制,当前位是1,则加上对应的方法数。tot表示当前已有多少个“1”,如果当前“1”的个数等于已知k,那么就不能向下算了,直接退出。退出之后看一下,要是tot==k那么还得加1,要是循环到最后一位还没到k也是有可能的==再有就是它问小于n的方法数 ,所以n==1特判,开始居然写成了==0 ╮(╯▽╰)╭

/*********
nefu1035
2016.2.16
864k 	3ms	C++ (g++ 3.4.3)
**********/
#include <iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
int f[32][32];
void init()
{
    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 cal(int n,int k,int b)
{
    vector<int>c;
    int ans=0,tot=0;
    while(n)
    {
        c.push_back(n%b);
        n/=b;
    }
    for(int i=c.size()-1;i>=0;i--)
    {
        if(c[i]==1)
        {
            ans+=f[i][k-tot];
            tot++;
            if(tot==k) break;
        }
    }
    if(tot==k) ans++;
    return ans;
}
int main()
{
   // freopen("cin.txt","r",stdin);
    init();
    int n,k;
    while(~scanf("%d%d",&n,&k))
    {
        if(n==1)
        {
            if(k==0) printf("1\n");
            else printf("0\n");
            continue;
        }
        printf("%d\n",cal(n-1,k,2));
    }
    return 0;
}
















你可能感兴趣的:(dp,二进制)