MELON的难题

题目描述

MELON 有一堆精美的雨花石(数量为n,重量各异),准备送给S和W。MELON希望送给俩人的雨花石重量一致,请你设计一个程序,帮MELON确认是否能将雨花石平均分配。

输入描述

第1行输入为雨花石个数:n,0<n<31。

第2行输入为空格分割的各雨花石重量:m[0]m[1].. m[n -1],0<m[k]< 1001.

不需要考虑异常输入的情况。

输出描述

如果可以均分,从当前雨花石中最少拿出几块,可以使两堆的重量相等;如果不能均分,则输出-1。

示例1

输入

4

1 1 2 2

输出

2

说明

输入第一行代表共4颗雨花石,

第二行代表4颗雨花石重量分别为1、1、2、2。均分时只能分别为1,2,需要拿出重量为1和2的两块雨花石,所以输出2。

示例2

输入

10

1 1 1 1 1 9 8 3 7 10

输出

3

说明

输入第一行代表共10颗雨花石,

第二行代表4颗雨花石重量分别为1、1、1、1、1、9、

8、3、7、10。

均分时可以1.1.1.1.1.9.7和10.8.3,也可以1.1.1.1.9.8和10,7.3,1,或者其他均分方式,但第一种只需要拿出重量为10,8,3的3块雨花石,第二种需要拿出4块,所以输出3(块数最少)。

解题思路

这个问题本质上是一个“子集和”问题,即给定一组不同重量的雨花石,判断是否可以将这些雨花石分成两个子集,使得两个子集的重量相等。如果能分,则还需要计算最少拿出的雨花石数量以实现这个目标。

1. 判断是否可以平分

首先需要判断这些雨花石的总重量 total_weight 是否是偶数:

- 如果 total_weight 是奇数,那么肯定无法平分,直接输出 -1,因为两个子集的重量不可能相等。

- 如果 total_weight 是偶数,目标就是将雨花石分成两个重量为 total_weight / 2 的子集。

2. 转换为背包问题

将问题转化为经典的“背包问题”:

- 我们需要在给定的雨花石集合中,找到一个子集,其总重量恰好等于 total_weight / 2。如果能找到这个子集,说明可以平分雨花石。

- 同时,还要最小化子集的元素数量。

3. 动态规划的核心思想

使用动态规划解决这个问题。设 dp[i] 为达到重量 i 时所需的最少雨花石数量。

- 初始化:dp[0] = 0,即凑出重量 0 不需要任何雨花石。

- 其余的 dp[i] 初始化为一个很大的值(比如无穷大 ∞),表示还无法凑出重量 i。

- 遍历每个雨花石的重量,并更新动态规划数组 dp,确保每个雨花石只使用一次:

  - 对每个雨花石 weights[i],从目标重量 target 开始,逐步往回更新到 weights[i]:

    - 如果已经能够凑出重量 j - weights[i](即 dp[j - weights[i]] 不是无穷大),那么更新 dp[j] 为:

      dp[j] = min(dp[j], dp[j - weights[i]] + 1)

    - 这里 dp[j - weights[i]] + 1 表示加入当前雨花石后,能够凑出重量 j,并更新所需雨花石的最小数量。

 4. 判断最终结果

- 在遍历完所有雨花石后,检查 dp[target]的值。

  - 如果 dp[target] 仍然是初始值(例如 ∞),说明无法凑出重量为 target 的子集,即不能平分雨花石,此时输出 -1。

  - 否则,dp[target] 的值就是达到目标重量所需的最少雨花石数量,直接输出这个值。

python解法:

def min_stones_to_split(n, weights):
    total_weight = sum(weights)
    
    # 如果总重量是奇数,无法平分
    if total_weight % 2 != 0:
        return -1

    target = total_weight // 2

    # dp[i] 表示凑出重量 i 所需的最少雨花石数量
    dp = [float('inf')] * (target + 1)
    dp[0] = 0  # 凑出重量 0 需要 0 块雨花石

    # 遍历每个雨花石重量
    for weight in weights:
        for j in range(target, weight - 1, -1):
            if dp[j - weight] != float('inf'):
                dp[j] = min(dp[j], dp[j - weight] + 1)

    # 如果 dp[target] 仍然是 float('inf'),说明无法平分
    return dp[target] if dp[target] != float('inf') else -1

# 测试
if __name__ == "__main__":
    n = int(input())  # 雨花石个数
    weights = list(map(int, input().split()))  # 输入雨花石重量
    result = min_stones_to_split(n, weights)
    print(result)

​​​​​​​