题401.数位dp-acwing-1081. 度的数量
一、题目
二、题解
欲求区间[X,Y]中满足性质的数的个数,我们可以想着去求小于m的数中满足性质的个数f(m),然后利用前缀和思想,f(X,Y)=f(Y)-f(X-1)得到最后结果。
现在考虑如何求f(m):
对于上界m,设按B进制表示为an-1an-2an-3an-4…a1a0,则我们可以据此来给每一位填数,填0或者1(因为是要求<=m的数中恰好等于K个互不相等的B的整数次幂之和的数的个数,显然每个加数的系数为1),则以填最高位为例,新数该位要么填0-an-1-1(当然本题只能最大到1),要么填an-1(当然也是最大只能填1),对于前者之后的位不论怎么填都可以满足比n来得小,则总的方案数就是加上C(n,K-last)+C(n,K-last-1),其中last为之前填过的1的个数,对于后者则last++,然后须按照上述方式接着讨论,则整个过程可以按树讨论如下:
则我们还需要开局预处理一个组合数,对此可利用C(i,j)=C(i-1,j)+C(i-1,j-1)递推求取。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn=35;//B进制最小为2,则由题目给的X、Y数据范围maxn设为>32
int K,B,l,r;
int f[maxn][maxn];//f[i][j]表示i个数里头选j个数出来的方案数,即组合数C(i,j)
//求组合数
void init()
{
for(int i=0;i<maxn;i++)
{
for(int j=0;j<=i;j++)
{
if(!j) f[i][j]=1;//i个数里头选0个数的方案数为1
else f[i][j]=f[i-1][j]+f[i-1][j-1];//C(i,j)=C(i-1,j-1)+C(i-1,j)
}
}
}
int dp(int n)//求<=n的满足性质的数的个数
{
if(!n) return 0;//
//分离出在B进制下n的各个位
vector<int> nums;
while(n) nums.push_back(n%B),n/=B;
int res=0;//方案数
int last=0;//存储之前用过的1的个数
for(int i=nums.size()-1;i>=0;i--)//从高位向低位枚举B进制下n的各个位
{
int x=nums[i];
if(x>0)//x>0时才有左分支(填0到x-1范围内的数,但注意本题只能填0到1)
{
res+=f[i][K-last];//左分支,当前位填0,则方案数加上从i位中挑K-last个位来填1的方案数
if(x>1)//左分支,当前位要填1,必须x>1
{
if(K-last-1>=0) res+=f[i][K-last-1];//当前位填1,则方案数加上从i位中挑K-last-1个位来填1的方案数
break;//右分支,当前位填x,则必定不满足数性质(它是只能填0、1),直接break
}
else//x==1则左分支,当前位只能填0
{
last++;//考虑右分支,当前位填x,即1,则之前用过的1的个数++
if(last>K) break;//之前用过的1的个数超过了能够填1的个数直接break
}
}
if(!i&&last==K) res++;//遍历到最低位,且最低一位只能为0(上面操作考虑过了该位填1的情况),此时res++
}
return res;
}
int main()
{
init();//预处理组合数
cin>>l>>r>>K>>B;
cout<<dp(r)-dp(l-1);//f(X,Y)=f(Y)-f(X-1)
}
三、关于数位dp
技巧1:[X,Y] => f(Y)-f(X-1)
技巧2:树