题解:AcWing 3417 砝码称重

【题目来源】

AcWing:3417. 砝码称重 - AcWing题库

【题目描述】

你有一架天平和 \(N\) 个砝码,这 \(N\) 个砝码重量依次是 \(W_1,W_2,⋅⋅⋅,W_N\)

请你计算一共可以称出多少种不同的正整数重量?

注意砝码可以放在天平两边。

【输入】

输入的第一行包含一个整数 \(N\)

第二行包含 \(N\) 个整数:\(W_1,W_2,W_3,⋅⋅⋅,W_N\)

【输出】

输出一个整数代表答案。

【输入样例】

3
1 4 6

【输出样例】

10

【核心思想】

  1. 问题分析:给定 \(N\) 个砝码,重量分别为 \(W_1, W_2, \dots, W_N\)。每个砝码可以放在天平左边(加重量)、放在天平右边(减重量)或不使用。求能称出的不同正整数重量个数。这是一个布尔型DP问题,关键在于每个砝码有三种状态选择。

  2. 算法选择

    • 布尔型二维DPf[i][j] 表示前 \(i\) 个砝码是否能称出重量 \(j\)(true/false)
    • 三状态转移:不放(继承)、放左边(+W[i])、放右边(-W[i],用绝对值处理)
  3. 关键步骤

    • 计算总重量m = sum(W[i]),最大可称重量不超过总重量
    • 初始化f[0][0] = true,表示0个砝码可以称出重量0
    • 状态转移:对于第 \(i\) 个砝码,遍历所有可能的重量 \(j\)\(0 \leq j \leq m\)
      • 不放:f[i][j] = f[i-1][j]
      • 放右边(对侧,相当于加在右边):若 \(j + W[i] \leq m\),则 f[i][j] |= f[i-1][j+W[i]]
      • 放左边(同侧,相当于减掉重量):f[i][j] |= f[i-1][abs(j-W[i])]
    • 统计答案:遍历所有正整数重量 \(j\)\(1 \leq j \leq m\)),若存在某个 \(i\) 使 f[i][j] == true,则重量 \(j\) 可达,ans++
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \times m)\),其中 \(m = \sum W[i]\) 为所有砝码总重量
    • 空间复杂度:\(O(N \times m)\),二维布尔数组
  5. 布尔型DP(天平放置)的核心思想

    • 三状态选择:每个砝码有三种选择,本质是01背包的扩展
    • 重量范围覆盖:由于重量可能通过减法产生(放右边),DP表覆盖了从0到总重量 \(m\) 的范围
    • 绝对值处理:使用 abs(j-W[i]) 处理减法后的非负重量
    • 统计策略:检查每个重量是否在任意前 \(i\) 个砝码时可达
    • 适用于"天平称重"、"正负选择"类问题

【解题思路】

img/Pasted image 20260610161143.png

【算法标签】

01背包

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 100005;  // 定义最大物品数N和最大总重量M
int n, m;  // 物品数量n,总重量m
int w[N];  // 每个物品的重量
bool f[N][M];  // 动态规划数组,f[i][j]表示前i个物品能否称出重量j
int main()  // 主函数
{
    cin >> n;  // 输入物品数量
    for (int i=1; i<=n; i++)  // 读取每个物品的重量
    {
        cin >> w[i];  // 输入第i个物品的重量
        m += w[i];  // 累加总重量
    }
    f[0][0] = 1;  // 初始状态:0个物品可以称出重量0
    for (int i=1; i<=n; i++)  // 遍历每个物品
        for (int j=0; j<=m; j++)  // 遍历所有可能的重量
        {
            f[i][j] = f[i-1][j];  // 不使用第i个物品
            if (j+w[i]<=m)  // 如果将物品放在对侧(加在右边)
            {
                f[i][j] |= f[i-1][j+w[i]];  // 可以从j+w[i]转移过来
            }
            f[i][j] |= f[i-1][abs(j-w[i])];  // 如果将物品放在同侧(减掉重量)
        }
    int ans = 0;  // 可称出的不同重量数
    for (int j=1; j<=m; j++)  // 遍历所有可能的重量
        for (int i=0; i<=n; i++)  // 遍历所有物品数
            if (f[i][j])  // 如果该重量可以被称出
            {
                ans++;  // 计数加1
                break;  // 跳出内层循环
            }
    cout << ans << endl;  // 输出可称出的不同重量数
    return 0;  // 程序正常结束
}

【运行结果】

3
1 4 6
10
posted @ 2026-06-14 10:36  团爸讲算法  阅读(15)  评论(0)    收藏  举报