【题解】【位运算】—— [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 n≤10。
- 对于另外 20 % 20\% 20% 的数据,保证 n n n 为奇数。
- 对于另外 20 % 20\% 20% 的数据,保证 n n n 为 2 2 2 的正整数次幂。
- 对于 80 % 80\% 80% 的数据, n ≤ 1024 n \le 1024 n≤1024。
- 对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 10 7 1 \le n \le {10}^7 1≤n≤107。
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=1∗22+1∗21+0∗20所以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==0
时return
。
注意:我们的循环判断条件应该写成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;
}