首先上一道入门数位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 1≤X≤Y≤231−1,1≤K≤20,2≤B≤10
思路:
把数字 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 i−1 位就可以了
- x = 1 x = 1 x=1 :则 i i i 位上取 0 的时候,后面 i − 1 i -1 i−1 位都可以随意取值 ,取 1 的时候,后面 i − 1 i - 1 i−1位要再小于题目的数的前提下取值,并且能取 ( k − l a s t − 1 ) (k-last-1) (k−last−1) 个 1 ,所以要更新 l a s t last last
- x > 1 x > 1 x>1 :则 i i i 位上可以取 1 ,0 ,并且后面 i − 1 i -1 i−1 位可以随便取
这里 “ i − 1 i-1 i−1位随便取” 和 “对于 i − 1 i-1 i−1 位讨论” 的区别,就体现在前一个直接用组合数 f [ i − 1 ] [ k − l a s t ] f[i-1][k-last] f[i−1][k−last]就可以,后面则需要再进入循环去讨论。
最后对于最后一位一定要进行单独讨论,如果对于最后一位时 ,所有的
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
吓出一身冷汗,待补吧,也欢迎指正