【题解】【位运算】—— [CSP-J2020] 优秀的拆分

[CSP-J2020] 优秀的拆分
前置知识:迭代法,位运算,递归。

[CSP-J2020] 优秀的拆分

题目描述

一般来说,一个正整数可以拆分成若干个正整数的和。

例如, 1 = 1 1=1 1=1 10 = 1 + 2 + 3 + 4 10=1+2+3+4 10=1+2+3+4 等。对于正整数 n n n 的一种特定拆分,我们称它为“优秀的”,当且仅当在这种拆分下, n n n 被分解为了若干个不同 2 2 2正整数次幂。注意,一个数 x x x 能被表示成 2 2 2 的正整数次幂,当且仅当 x x x 能通过正整数个 2 2 2 相乘在一起得到。

例如, 10 = 8 + 2 = 2 3 + 2 1 10=8+2=2^3+2^1 10=8+2=23+21 是一个优秀的拆分。但是, 7 = 4 + 2 + 1 = 2 2 + 2 1 + 2 0 7=4+2+1=2^2+2^1+2^0 7=4+2+1=22+21+20 就不是一个优秀的拆分,因为 1 1 1 不是 2 2 2 的正整数次幂。

现在,给定正整数 n n n,你需要判断这个数的所有拆分中,是否存在优秀的拆分。若存在,请你给出具体的拆分方案。

输入格式

输入只有一行,一个整数 n n n,代表需要判断的数。

输出格式

如果这个数的所有拆分中,存在优秀的拆分。那么,你需要从大到小输出这个拆分中的每一个数,相邻两个数之间用一个空格隔开。可以证明,在规定了拆分数字的顺序后,该拆分方案是唯一的。

若不存在优秀的拆分,输出 -1

样例 #1

样例输入 #1

6

样例输出 #1

4 2

样例 #2

样例输入 #2

7

样例输出 #2

-1

提示

样例 1 解释

6 = 4 + 2 = 2 2 + 2 1 6=4+2=2^2+2^1 6=4+2=22+21 是一个优秀的拆分。注意, 6 = 2 + 2 + 2 6=2+2+2 6=2+2+2 不是一个优秀的拆分,因为拆分成的 3 3 3 个数不满足每个数互不相同。


数据规模与约定

  • 对于 20 % 20\% 20% 的数据, n ≤ 10 n \le 10 n10
  • 对于另外 20 % 20\% 20% 的数据,保证 n n n 为奇数。
  • 对于另外 20 % 20\% 20% 的数据,保证 n n n 2 2 2 的正整数次幂。
  • 对于 80 % 80\% 80% 的数据, n ≤ 1024 n \le 1024 n1024
  • 对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 10 7 1 \le n \le {10}^7 1n107

1.迭代法

1.1.题意分析

    我们首先要解决判断的问题。容易发现,只有2的整数次幂才可以形成优秀的拆分。我们只需要提取一个公因数就可以看出来。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int num;
	scanf("%d",&num);
    if(num%2==1)//判断能否拆分
    {
    	printf("%d\n",-1);
    	return 0;//直接退出程序
	}
	return 0;
}

    接下来,我们就要解决从大到小输出的问题。可以发现, 1 0 7 10^7 107 的数据量绝对不会超过 2 25 2^{25} 225。这里我们只需要从25开始从大到小循环2的整数次幂并判断就可以了。

1.2.代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int num;
	scanf("%d",&num);
    if(num%2==1)//判断能否拆分
    {
    	printf("%d\n",-1);
    	return 0;//直接退出程序
	}
	for(int i=25;i>=1;i--)//从大到小判断,最大不可能超过2^25 
		if((1<<i)<=num)//输出,1<<i代表1*2^i
		{
			printf("%d ",1<<i);//输出这个数
			num-=(1<<i);//减去它
		}
	puts("");//输出换行
	return 0;
}

2.位运算

2.1.题意分析

    观察每个可以被优秀的拆分的数,它们的二进制形式的第i位就代表了这个数有几个 2 i 2^i 2i ,比如 6 6 6

6 = ( 110 ) 2 ( 110 ) 2 = 1 ∗ 2 2 + 1 ∗ 2 1 + 0 ∗ 2 0 所以 6 = 4 + 2 6=(110)_2\\ (110)_2=1* 2^2+1* 2^1 +0* 2^0 \\ 所以6=4+2 6=(110)2(110)2=122+121+020所以6=4+2

    其他的数也可以仿照这个方法得出答案。我们可以使用&按位与判断这个数的二进制表达形式的第i位是否有 1 1 1

    注意:我们只需要从25位起开始判断就可以了。可以发现, 1 0 7 10^7 107 的数据量绝对不会超过 2 25 2^{25} 225

2.2.代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int num;
	scanf("%d",&num);
    if(num%2==1)//判断能否拆分
    {
    	printf("%d\n",-1);
    	return 0;//退出程序
	}
	/*将一个数转化为2进制,如6=1*2^2+1*2^1+0*2^0=110(2),也就是从最高位开始判断,
	  第i为如果有1,那么这个数就有一个2^i*/
	for(int i=25;i>=1;i--)//
	    if((num>>i)&1)//看num左移i位后的最高位,也就是第i位是否有1,可以自己画图理解
		    printf("%d ",1<<i);//输出这个数
    puts("");//输出换行
	return 0;
}

3.递归

3.1.题意分析

    换一种思路,我们可以从1开始,寻找最大的不超过n的2的整数次幂数。递归边界就是n==0return

    注意:我们的循环判断条件应该写成n/2>=i。当 i i i超过 n / 2 n/2 n/2时就说明找到了可以分解的数。可以自己带数看一看,理解其中的原理。

3.2.代码

#include<bits/stdc++.h>
using namespace std;
void split(int n)//分解函数
{
	if(n==0)return;//递归边界
	int i=1;//从1开始
	while(n/2>=i)//从大判断 
	    i<<=1;//等同于i*=2
	printf("%d ",i);
	split(n-i);//递归处理剩下的 
}
int main()
{
    int num;
	scanf("%d",&num);
    if(num%2==1)//判断能否拆分
    {
    	printf("%d\n",-1);
    	return 0;
	}
	split(num);//调用分解函数
	puts("");//输出换行
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子教编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值