题意:
题目就是问:在一个区间[x,y]
内,有多少个符合题意的数,
“符合题意的数”是指:这个数的B
进制表示中,其中有K
位是1
、其他位全是0
。
比如样例中B=2,K=2
,这个数是18
.就是把18
化为二进制,18
化为二进制数为:10010
,其中有2
位1
,其他位都是0
.
再比如 B=3,K=2
,这个数是17
.就是把17
化为三进制,17
化为三进制为110
,其中也有2
个1
,其余都是0
.
思路:
这是一道 数位dp 的题目,数位dp的题目的问法一般如下:
- 某个区间内,满足某种性质的数的个数。
对于 数位dp问题 一般有 两个解题方向:
- ①利用 前缀和 的思想,比如求区间
[x, y]
中满足条件的数的个数,转化成求[0, y]
的个数、[0, x-1]
的个数 两者之差 - ②利用 树的结构 来考虑(其本质即为:按位分类讨论)
该类题目的核心就是根据数位进行分类讨论。
-
第一步,先将数
x
转化成为B
进制,记为N
,将转化后的B
进制数中的每一位存入一个num
数组中。 -
第二步,在
num
数组中从高位到低位 依次 进行分类讨论,对于第i
位数字x
有三种情况(k
含义见“题意”,last
表示第i
位之前的所有位取过的1
的个数) -
- ①
x = 0
:此时第i
位只能取0
,不影响1
的个数,后面所有位(共i
位,因为下标是从0
开始的,下同)可以取k-last
个1
,直接讨论后面i
位即可。
- ①
-
- ②
x = 1
:此时可以取0
或1
,当 第i
位取0
时,后面i
位都可以随意取值,可取k - last
个1
,当取1
的时候,后面i
位要在小于题目给定数的前提下取值,还要进行讨论,后面i
位不能随便取,也就不是组合数(且只能取k - last - 1
个1
)。
- ②
-
- ③
x > 1
:则i
位上 可以取1 ,0
,并且后面i
位可以随意取k-last-1个1。除了0 、1的其他值不能取,因为其他值对于本题无意义(换句话说就是不符合题意)。
- ③
我们可以构造一棵递归搜索树,我们要求的答案 res 即为下图所有圆圈部分之和
另外,组合数递推公式:
,我们将组合数存在数组 c
中,c[i][j]
表示 从 i
个数中选出 j
个数的组合数。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 35;
int K, B;
int c[N][N];
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-1] + c[i-1][j];
}
}
}
int dp(int n)
{
if (!n) return 0;//如果 n==0,那么就直接返回 0(特判)
vector<int> num;
while (n) num.push_back(n%B), n /= B;
int res = 0;
int last = 0;//表示已经取了多少个 1
for(int i=num.size()-1; i>=0; --i)//从最高位到最低位对每一位数讨论
{
int x = num[i];
if(x) //求左分支中数的个数
{
res += c[i][K-last];//加上第 i 位取 0 的时候的组合数,也就是对于后面 i 位取 k-last 个 1 的数量
if(x > 1)//如果 x>1,就可以直接用组合数表示出来,不用进行讨论,也就是 i 位取 1 的时候,后面 i 位随便取 k-last-1 个 1
{
if(K - last - 1 >= 0) res += c[i][K-last-1];
break;
}
else//如果 x==1,那么 i 位取 1 的时候,还要进行讨论,后面 i 位不能随便取,也就不是组合数
{
++last;
if(last > K) break;
}
}
if (!i && last==K) ++res;// 最右侧分支上的方案,即对于最后一位进行的特殊考虑
}
return res;
}
int main()
{
init();
int l, r;
cin>>l>>r>>K>>B;
cout<<dp(r) - dp(l-1)<<'\n';
return 0;
}