[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

相关推荐
Tisfy1 天前
LeetCode 3296.移山所需的最少秒数:优先队列
算法·leetcode·题解·优先队列·模拟
I_LPL3 天前
hot 100 普通数组、矩阵专题
java·数据结构·矩阵·动态规划·贪心·数组·求职面试
进击的荆棘3 天前
优选算法——模拟
java·开发语言·算法·模拟
问好眼17 天前
《算法竞赛进阶指南》0x01 位运算-3.64位整数乘法
c++·算法·位运算·信息学奥赛
问好眼17 天前
《算法竞赛进阶指南》0x01 位运算-2.增加模数
c++·算法·位运算·信息学奥赛
问好眼17 天前
《算法竞赛进阶指南》0x01 位运算-4.最短Hamilton路径
c++·算法·动态规划·位运算·信息学奥赛
zaiyang遇见17 天前
[GESP202509 六级] 货物运输
深度优先·贪心·树的遍历·gesp六级·树的存储
问好眼17 天前
《算法竞赛进阶指南》0x01 位运算-1.a^b
c++·算法·位运算·信息学奥赛
List<String> error_P22 天前
蓝桥杯高频考点练习:模拟问题“球队比分类”
数据结构·python·算法·模拟·球队比分