文章目录
- [P7071 [CSP-J 2020] 优秀的拆分](#P7071 [CSP-J 2020] 优秀的拆分)
P7071 [CSP-J 2020] 优秀的拆分
题目描述
一般来说,一个正整数可以拆分成若干个正整数的和。
例如, 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
cpp
6
输出 #1
cpp
4 2
输入输出样例 #2
输入 #2
cpp
7
输出 #2
cpp
-1
输入输出样例 #3
输入 #3
cpp
126
输出 #3
cpp
64 32 16 8 4 2
说明/提示
样例 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。
提交链接
思路分析
题目要求 📌
是否可以把一个正整数 n n n 拆分成 若干个不同的 2 的正整数次幂之和。
也就是说: n = 2 a 1 + 2 a 2 + . . . + 2 a k n = 2^{a_1} + 2^{a_2} + ... + 2^{a_k} n=2a1+2a2+...+2ak
并满足: a i ≥ 1 a_i ≥ 1 ai≥1 且 每个数 互不相同
因此允许的数只有:2, 4, 8, 16, 32, ...
而: 1 = 2 0 1 = 2^0 1=20,❌ 不属于 2 的正整数次幂,因此不允许出现。
❌ 1 不合法
而 奇数的二进制最低位一定是 1 1 1,一定会出现 1 1 1。
所有奇数一定不存在优秀拆分
如果 n 是奇数 → 直接输出 -1
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
if(n % 2 == 1)
{
cout << -1;
}
return 0;
}
⭐上面的代码提交洛谷,能得 30 % 30\% 30% 的分数。
观察题目数据范围 🔍
题目给出一组特殊数据:对于另外 20% 的数据,保证 n 为 2 的正整数次幂
例如:
| n | 正确拆分 |
|---|---|
| 2 | 2 |
| 4 | 4 |
| 8 | 8 |
| 16 | 16 |
可以发现一个重要性质:
如果 n 本身就是 2 的正整数次幂 ,那么最简单的拆分就是:n = n
满足题目所有条件:
- 是 2 的正整数次幂 ✅
- 没有重复数字 ✅
因此这是一个 合法拆分。
代码实现思路 💡
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
if(n % 2 == 1)
{
cout << -1;
}
else
{
cout << n;
}
return 0;
}
⭐上面的代码提交洛谷,能得 55 % 55\% 55% 的分数。
继续观察数据范围 🔍
题目还有一组数据:对于 20% 的数据,n ≤ 10
因此 n 只可能是:1 2 3 4 5 6 7 8 9 10
可以逐个分析:
| n | 是否可拆分 | 一种合法拆分 |
|---|---|---|
| 1 | ❌ | - |
| 2 | ✔ | 2 |
| 3 | ❌ | - |
| 4 | ✔ | 4 |
| 5 | ❌ | - |
| 6 | ✔ | 4 + 2 |
| 7 | ❌ | - |
| 8 | ✔ | 8 |
| 9 | ❌ | - |
| 10 | ✔ | 8 + 2 |
在 1 ∼ 10 1 \sim 10 1∼10 的范围内,还需要两个数需要 特殊拆分
cpp
6 = 4 + 2
10 = 8 + 2
因此在原有代码基础上增加两个特判:
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
if(n % 2 == 1)
{
cout << -1;
}
else
{
if(n == 6)
{
cout << "4 2";
}
else if(n == 10)
{
cout << "8 2";
}
else
cout << n;
}
return 0;
}
⭐上面的代码提交洛谷,能得 60 % 60\% 60% 的分数。
分析 偶数是否一定存在优秀拆分 🤔
题目要求把一个整数 n 拆分成:
若干个不同的 2 的正整数次幂 ,也就是:2, 4, 8, 16, 32 ...
利用二进制表示 🔍
任何整数都可以写成 二进制形式 : n = b 0 ∗ 2 0 + b 1 ∗ 2 1 + b 2 ∗ 2 2 + . . . n = b_0*2^0 + b_1*2^1 + b_2*2^2 + ... n=b0∗20+b1∗21+b2∗22+...
例如:
| n | 二进制 | 拆分 |
|---|---|---|
| 6 | 110 | 4 + 2 |
| 10 | 1010 | 8 + 2 |
| 12 | 1100 | 8 + 4 |
可以发现:二进制中的每一位 1 都对应一个 2^i。
因此二进制本身就提供了一种 不重复的拆分方式。
如果 n 是 偶数 ,那么:n 的二进制最低位一定是 0
也就是说:不会出现 2 0 2^0 20
例如:
cpp
6 = 110 -> 4 + 2
10 = 1010 -> 8 + 2
12 = 1100 -> 8 + 4
126=1111110 -> 64 + 32 + 16 + 8 + 4 + 2
核心结论 ⭐
奇数 : 一定无解
偶数 : 一定存在优秀拆分
满分思路:贪心拆分 / 二进制拆分 🚀
方法一:二进制拆分(推荐)🧠
核心思想
任何整数都可以表示为 二进制形式 : n = b 0 ∗ 2 0 + b 1 ∗ 2 1 + b 2 ∗ 2 2 + . . . n = b_0*2^0 + b_1*2^1 + b_2*2^2 + ... n=b0∗20+b1∗21+b2∗22+...
只要把二进制中为 1 1 1 的位对应的 2 i 2^i 2i 输出即可。
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
if(n & 1)
{
cout << -1;
return 0;
}
for(int i = 32; i >= 0; i--)
{
if(n >> i & 1)
{
cout << (1 << i) << " ";
}
}
return 0;
}
代码解析
判断奇偶:if(n & 1),等价于: n % 2 = = 1 n \% 2 == 1 n%2==1
如果为奇数:输出 − 1 -1 −1
枚举二进制位:for(int i = 32; i >= 0; i--)
从 高位到低位枚举 ,保证输出顺序:从大到小
判断某一位是否为 1:if(n >> i & 1)
含义:
cpp
把 n 右移 i 位
判断最低位
如果为 1,说明存在: 2 i 2^i 2i, 输出对应的 2 2 2 的幂: ( 1 < < i ) (1 << i) (1<<i)
时间复杂度: O ( l o g n ) O(log n) O(logn)
方法二:贪心拆分 💡
每次选择:不超过 n 的最大 2 2 2 的幂,然后减去它。
例如: n = 26 n = 26 n=26
第一步:最大 2 k ≤ 26 2^k ≤ 26 2k≤26,得到: 16 16 16
剩余:26 - 16 = 10
第二步:最大 2 k ≤ 10 2^k ≤ 10 2k≤10,得到: 8 8 8
剩余:10 - 8 = 2
最终:26 = 16 + 8 + 2
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
if(n % 2 == 1)
{
cout << -1;
}
else
{
while(n != 0)
{
int c = 1;
while(c * 2 <= n)
{
c = c * 2;
}
cout << c << " ";
n -= c;
}
}
return 0;
}
代码解析
找最大 2 的幂
cpp
int c = 1;
while(c * 2 <= n)
{
c *= 2;
}
循环结束后: c = c = c= 最大的 2 k ≤ n 2^k ≤ n 2k≤n
输出并减去
cpp
cout << c << " ";
n -= c;
不断重复直到: n = 0 n = 0 n=0