第12届蓝桥国赛_二进制问题(数位dp)

首先上一道入门数位dp(比蓝桥的都难一些QAQ)

题目:

求给定区间 [ X , Y ] [X,Y] [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K K K互不相等 B B B 的整数次幂之和

范围:

1 ≤ X ≤ Y ≤ 2 31 − 1 , 1 ≤ K ≤ 20 , 2 ≤ B ≤ 10 1≤X≤Y≤2^{31}−1,1≤K≤20,2≤B≤10 1XY2311,1K20,2B10

思路:

把数字 X , Y X,Y X,Y 分解为 B B B进制数 ,每位上的数字存入数组,然后从高位往低位讨论,想象形成一棵向右扩展的二叉树的过程,对于第 i i i 位数字 x x x 有三种情况

  • x = 0 x = 0 x=0 :则 i i i 位上只能取 0 ,所以直接讨论 i − 1 i-1 i1 位就可以了
  • x = 1 x = 1 x=1 :则 i i i 位上取 0 的时候,后面 i − 1 i -1 i1 位都可以随意取值 ,取 1 的时候,后面 i − 1 i - 1 i1位要再小于题目的数的前提下取值,并且能取 ( k − l a s t − 1 ) (k-last-1) (klast1) 个 1 ,所以要更新 l a s t last last
  • x > 1 x > 1 x>1 :则 i i i 位上可以取 1 ,0 ,并且后面 i − 1 i -1 i1 位可以随便取

这里 “ i − 1 i-1 i1位随便取” 和 “对于 i − 1 i-1 i1 位讨论” 的区别,就体现在前一个直接用组合数 f [ i − 1 ] [ k − l a s t ] f[i-1][k-last] f[i1][klast]就可以,后面则需要再进入循环去讨论。

最后对于最后一位一定要进行单独讨论,如果对于最后一位时 ,所有的 k k k 1 1 1都已经取好了,也就是k==last了,才做res++

代码:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 35;
int K, B;
int 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];
}

int dp(int n){
    if (!n) return 0;

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

    int res = 0;
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- ){
        int x = nums[i];
        if (x)  {// 求左边分支中的数的个数
        	//当前位填0,从剩下的所有位(共有i位)中选K-last个数
            res += f[i][K - last];
            if (x > 1){
				//当前位填1,从剩下的所有位(共有i位)中选K-last-1个数,没有右子树,随便选着填
                if (K - last - 1 >= 0) res += f[i][K - last - 1];
				//高位(>1)没机会填,丢掉
                break;
            }
            else{ //x=1,左分支只能取0,右分支为1,进入右子树
                last ++ ;
                if (last > K) break;//填不了了,进不去右子树
            }
        }
        //一共要有k个1才行
        if (!i && last == K) res ++ ;   // 最右侧分支上的方案
    }

    return res;
}

int main(){
    init();
    int l, r;
    cin >> l >> r >> K >> B;
    cout << dp(r) - dp(l - 1) << endl;
    return 0;
}

蓝桥的题目:
在这里插入图片描述
显然是弱化版,直接从1开始,省去相减,而且2进制,省去数组的转换保存,直接上代码(命名为c2):

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
typedef long long ll;
using namespace std;
const int N = 70;
ll K, B=2, l=0, r;
ll f[70][70];//组合数
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){
    if (!n) return 0;

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

    ll res = 0;
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- ){
        int x = nums[i];
        if (x)  {// 求左边分支中的数的个数
        	//当前位填0,从剩下的所有位(共有i位)中选K-last个数
            res += f[i][K - last];
			//x=1,左分支只能取0,右分支为1,进入右子树
            last ++ ;
            if (last > K) break;//填不了了,进不去右子树

        }
        if (!i && last == K) res ++ ;   // 最右侧分支上的方案
    }

    return res;
}

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

然后从网上找了一份代码来对拍(命名为c1):

代码传送门
在这里插入图片描述
写了随机数生成(命名为c3):

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
ll mod=1e18;
int main(){
	srand(time(0));
	ll n,m,S;
	n=1ll*rand()*rand()*rand()*rand()%mod+1;
	m=rand()%50+1;
	cout<<n<<" "<<m;
	return 0;
}

对拍器:

@echo off  
:loop  
    c3.exe > c3.txt
    c2.exe < c3.txt > c2.txt
    c1.exe < c3.txt > c1.txt
    fc c2.txt c1.txt
if not errorlevel 1 goto loop  
pause
goto loop

没有问题:
在这里插入图片描述

特别注意:

f f f数组第一维要至少大于1e18所对应的二进制数的位数,因为会访问到 f f f数组的这个位置,不能想当然拿 k k k的范围照搬,这是在对拍到1e18左右的数据时才发现的😅

关键补更:
在这里插入图片描述
这组输入数据:12186109017870481 1

吓出一身冷汗,待补吧,也欢迎指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值