【题目描述】
原题来自:NEERC 2000 Central Subregional,题面详见 Ural 1057。题目
求给定区间 [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。
【输出格式】
只包含一个整数,表示满足条件的数的个数。
【样例输入】
15 20
2
2
【样例输出】
3
【数据范围与提示】
对于全部数据,1≤X≤Y≤2^31−1,1≤K≤20,2≤B≤10
思路:这道题是真的有难度,我也研究了很久,然后其实就是要转化成二进制来做,这是最好的办法,但是也有用阶乘的,因为这是杨辉三角的代码,就是主部分,我用的是二进制,但是我也看了一下不是二进制的,不知道理解的对不对,反正就是大佬讲了,然后我记得多少就写多少。
然后先介绍二进制的,可能要不厚道的抄一下《刘聪的浅谈数位dp》
//https://wenku.baidu.com/view/d2414ffe04a1b0717fd5dda8.html
//这道题用二进制来求,也可以说,数位dp都和进制有一定的联系
//所以对于这道题来讲就是将十进制转化成二进制,然后求二进制中k个1的个数,就是题目要求的k
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[50];//储存要计算的数
int f[50][50];//f[i][j]表示前i个中选j个1的个数 //表示的是二进制中的1
int x,y,k,b;
void dfs()//定义一个函数来找合适的子树,因为我们是画出了一个二叉树的图
{
memset(f,0,sizeof(f));//先将f初始化
f[0][0]=1;//如果最开始的不等于1,就无法循环,所以要特殊处理最开始的
for(int i=1;i<33;i++)//数据范围是 2^31,保险起见,小于33就是 <=32,这个范围是保险的
{
f[i][0]=f[i-1][0];//继承状态,其实就是自己本身,因为他没有任何的范围可求
for(int j=1;j<=i;j++)//现在就是处理小范围,也就是动态规划的大规模转化成小规模
{
f[i][j]=f[i-1][j]+f[i-1][j-1];//这个是这道题的精髓公式,就是我们画出二叉树图之后,根据规律,求出来的一条公式,来继承状态
}
}
}
int solve(int x,int k)//统计[0..x]内二进制表示含k个1的数的个数
{
int len=-1;//因为范围的定义是从0开始到x,所以要将len定义为-1
while(x>0)
{
len++;
a[len]=x%b;
x/=b;
}
int tot=0,ans=0;//tot记录当前路径上已有的1的数量,ans表示答案
for(int i=len;i>=0;i--)//从后往前寻找,因为我们表示的是0~x,所以要从最后一个往前
{
if(a[i]==1)//如果为1,就是找到了一个二进制的1,则依次求解
{
ans+=f[i][k-tot];//k是要求的1的个数,tot是当前找到的1的个数,f[i][k-tot]就是记录这一串数字加起来的结果,存储结果
tot++;//tot的个数增加1 ,tot表示二进制1的个数
if(tot==k) break;//如果到达的要求的个数,就跳出循环
}
else if(a[i]>1)//假如大于1的话,相当于所有的位可以为 1,所以直接求解跳出
{
ans+=f[i+1][k-tot];//就不是加当前的了,因为当前的不可以,而是加之前的,答案也就是之前的了
break;//跳出循环
}
}
if(tot==k) ans++;//每一次都要进行这一步,for循环之后直接判断,
//这个时候的ans就是一开始定义的0,所以和前面的就没有冲突了,ans++,增加一种答案
return ans;//返回最终的答案
}
int main()
{
dfs();//先要预处理f
scanf("%d%d",&x,&y);
scanf("%d%d",&k,&b);
printf("%d\n",solve(y,k)-solve(x-1,k));//0~y中的答案,减去0~x-1的答案,就是x~y这个区间的答案
return 0;
}
还有一个其他样的
//另一种做法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int f[50];//f数组是用来存储阶乘,就是递减用到的
ll len;//表示这个数的位数
int x,y,k,b;
ll dfs(ll yy,ll xx)//最好不要反过来,不然后面会很麻烦,全部都要改过来
{
ll he=1;//就是记起来的和
if(xx>yy) return 0;
for(int i=1;i<=xx;i++) he*=(yy-i+1);
for(int i=2;i<=xx;i++) he/=i;
return he;//返回结果
}
int main()
{
scanf("%d%d",&x,&y);
scanf("%d%d",&k,&b);
f[1]=1; len=1;//初始化
if(x>y) swap(x,y);//排序,防止出错
while(f[len]*b<=y) f[++len]=f[len-1]*b;
ll tot=k;//tot记录二进制1的个数
ll ans1=0,ans2=0;//记录答案
for(int i=len;i>=1;i--)//递减
{
if(y-f[i]>=0 && tot>0)//>0的时候说明这种方法行不通,要减掉
{
ans1+=dfs(i-1,tot);//加上上一个数的结果
y-=f[i];//减去这一种阶乘的范围
tot--;//1的数量要减,到0的时候,就是答案
}
}
if(tot==0) ans1++;//0~y的答案
tot=k;//重新进行下一组的计算
for(int i=len;i>=1;i--)
{
if(x-f[i]>=0 && tot>0)//>0的时候说明这种方法行不通,要减掉
{
ans2+=dfs(i-1,tot);//加上上一个数的结果
x-=f[i];//减去这一种阶乘的范围
tot--;//1的数量要减,到0的时候,就是答案
}
}
if(tot==0) ans2++;//0~x-1的答案
printf("%lld\n",ans1-ans2);//最后输出要求的范围的答案就行了
return 0;
}
完美,然后好像就没有什么了。难度系数是:7
对了,两个代码有错的话,大佬们记得指出来