#10163. 「一本通 5.3 例 1」Amount of Degrees

题目描述

原题来自:NEERC 2000 Central Subregional,题面详见 Ural 1057题目

求给定区间 [X,Y][X,Y][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≤231−1,1≤K≤20,2≤B≤101\le X\le Y\le 2^{31}-1,1\le K\le 20,2\le B\le 101≤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 

对了,两个代码有错的话,大佬们记得指出来

猜你喜欢

转载自blog.csdn.net/qq_42367531/article/details/82459890