蓝桥杯2021年第十二届国赛真题-二进制问题

题目描述

小蓝最近在学习二进制。他想知道 1 1 1 N N N 中有多少个数满足其二进制表示中恰好有 K K K 1 1 1。你能帮助他吗?

输入格式

输入一行包含两个整数 N N N K K K

输出格式

输出一个整数表示答案。

输入样例
7 2
输出样例
3
数据范围

对于 30 % 30\% 30% 的评测用例, 1 ≤ N ≤ 1 0 6 1 ≤ N ≤ 10^6 1N106, 1 ≤ K ≤ 10 1 ≤ K ≤ 10 1K10
对于 60 % 60\% 60% 的评测用例, 1 ≤ N ≤ 2 × 1 0 9 1 ≤ N ≤ 2 × 10^9 1N2×109, 1 ≤ K ≤ 30 1 ≤ K ≤ 30 1K30
对于所有评测用例, 1 ≤ N ≤ 1 0 18 1 ≤ N ≤ 10^{18} 1N1018, 1 ≤ K ≤ 50 1 ≤ K ≤ 50 1K50


算法1(数位DP)

非常明显这道题目是典型的数位DP的题目,题干说到在某个区间,求满足某种条件的数的个数,即这道题可以用数位DP过掉,再分析一下时间复杂度,其数据范围在 1 0 18 10^{18} 1018上,从这里可以推断出时间复杂度大概率应该控制在 O ( l o g ( n ) ) O(log(n)) O(log(n)),此时数位DP正好可以将此题AC掉!还需注意的一点是此题会爆int,应该用long long

首先分析一下此题的数位DP的逻辑,如下图所示:
设一个数有 n n n 位,数 N = a n a n − 1 a n − 2 . . . . . . a 1 a 0 N=a_na_{n-1}a_{n-2}......a_1a_0 N=anan1an2......a1a0,需要选出 K K K 1 1 1
蓝桥杯2021年第十二届国赛真题-二进制问题_第1张图片
先预处理一下组合数,根据递推式:
C n k = C n − 1 k − 1 + C n − 1 k C_n^k=C_{n-1}^{k-1}+C_{n-1}^k Cnk=Cn1k1+Cn1k
蓝桥杯2021年第十二届国赛真题-二进制问题_第2张图片

void init()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ ) 
            if (!j) f[i][j] = 1;
            else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}

然后每一位的值以二进制的形式存入容器vector中:

	vector<int> nums;
    while (n) nums.push_back(n % 2), n /= 2;

利用一个reslast来分别记录答案和已经消耗 1 1 1 的数量。

	LL res = 0;
    int last = 0;

设每一位的值为 x x x
对于 x x x 来讲,只有当 x = 1 x =1 x=1 的时候才有意义,举个例子:
对于一个 011101111 011101111 011101111 这个数当首位填 1 1 1 的时候,就会比这个数字要大了。不符合题意,故只有在 x = 1 x = 1 x=1时,我们才会分填1填0
具体看如下代码:

LL dp(LL n)
{
    vector<int> nums;
    
    while (n) nums.push_back(n % 2), n /= 2;
    
    LL res = 0;
    int last = 0;
    
    for (int i = nums.size() - 1; i >= 0; i -- ) 
    {
        int x = nums[i];
        
        if (x)
        {
            /*当第i位选0时*/
            res += f[i][K - last];
            /*当第i位选1时*/
            if (x == 1)
            {
                last ++ ;
                /*如果已经消耗1的数量大于规定的数量时,结束算法*/
                if (last > K) break;
            }
        }
        /*当为最后一位时,如果所消耗1的数量恰好等于规定数量时,即最后一位符合要求*/
        if (!i && last == K) res ++ ;
    }
    
    return res;
}
完整代码
#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 60;

LL r, K;
LL f[N][N];

void init()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ ) 
            if (!j) f[i][j] = 1;
            else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}

LL dp(LL n)
{
    vector<int> nums;
    
    while (n) nums.push_back(n % 2), n /= 2;
    
    LL res = 0;
    int last = 0;
    
    for (int i = nums.size() - 1; i >= 0; i -- ) 
    {
        int x = nums[i];
        
        if (x)
        {
            /*当第i位选0时*/
            res += f[i][K - last];
            /*当第i位选1时*/
            if (x == 1)
            {
                last ++ ;
                /*如果已经消耗1的数量大于规定的数量时,结束算法*/
                if (last > K) break;
            }
        }
        /*当为最后一位时,如果所消耗1的数量恰好等于规定数量时,即最后一位符合要求*/
        if (!i && last == K) res ++ ;
    }
    
    return res;
}

int main()
{
    cin >> r >> K;
    
    init();
    
    cout << dp(r);
    
    return 0;
}

你可能感兴趣的:(蓝桥杯,蓝桥杯,动态规划,算法)