题目描述:
求给定区间 [X,Y]中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:
17=2^4+2^0
18=2^4+2^1
20=2^4+2^2
输入格式
第一行包含两个整数 X 和 Y,接下来两行包含整数 K 和 B。
输出格式
只包含一个整数,表示满足条件的数的个数。
数据范围
1≤X≤Y≤2^31−1
1≤K≤20
2≤B≤10
输入样例:
15 20
2
2
输出样例:
3
分析:
题目意思是给定一个区间[X,Y],求这个区间中有多少个数满足条件,即这个数的B进制表示中恰好由K个1,并且剩下的位置上的数都是0。首先,数位DP常用的技巧有:求区间[l,r]中满足条件数的个数,只需要求0到r中满足条件数的个数s[r]以及0到l-1中满足条件数的个数s[l-1],所求数的个数等于s[r] - s[l - 1],这个技巧类似于前缀和。也就是说,本题中我们只需要求0到n中有多少个数满足条件即可。
设n的B进制表示为an,an-1,...,ai,...,a1,a0。我们可以从高位向低位遍历这个B进制数。
当ai = 0时:这个位置只能放0,所以可以跳过;
当ai > 0时:
如果ai > 1,那么第i位可以放0也可以放1,并且后面的位置不管怎么取都不会超过n了,设last为已经放置1的个数,则在ai处放0时,只需要在后面的位置选K - last个放1即可,即一共C(i,K-last)种情况。在ai处放1时,后面i位还需要放置K - last - 1个1,一共C(i,K-last-1)种放法。
如果ai = 1,这个位置放0是可以的,也就是需要在后面的位置继续放K - last个1,也可以放1,但是后面的位置就不能随便放数了,因为可能超过n,所以放1就暂且先将last++,继续考察下一位。
边界情况:某时刻last超过了K,就可以退出枚举的循环了。如果n恰好是K个1和剩下的0组成的,则枚举到最后一位时,方案数应该加1。
本题涉及到组合数,可以先预处理出来需要的组合数,用递推式C(n,m) = C(n-1,m-1) + C(n-1,m)即可,表示第n个物品选与不选的两种情况。
#include <iostream>
#include <vector>
using namespace std;
const int N = 35;
int C[N][N],K,B;
void init(){
for(int i = 0;i < N;i++){
for(int j = 0;j <= i;j++){
if(!j) C[i][j] = 1;
else C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
}
}
int get(int n){
if(!n) return 0;
vector<int> num;
while(n) num.push_back(n % B),n /= B;
int res = 0,last = 0;
for(int i = num.size() - 1;i >=0;i--){
int x = num[i];
if(x){
res += C[i][K - last];
if(x > 1){
res += C[i][K - last - 1];
break;
}
else{
last++;
if(last > K) break;
}
}
if(!i && last == K) res++;
}
return res;
}
int main(){
int x,y;
cin>>x>>y>>K>>B;
init();
cout<<get(y) - get(x - 1)<<endl;
return 0;
}