题解:洛谷 P5194 Scales

【题目来源】

洛谷:P5194 [USACO05DEC] Scales S - 洛谷(luogu.com.cn)

【题目描述】

约翰有一架用来称牛的体重的天平。与之配套的是 \(N (1≤N≤1000)\) 个已知质量的砝码(所有砝码质量的数值都在 \(32\) 位带符号整数范围内)。

每次称牛时,他都把某头奶牛安置在天平的某一边,然后往天平另一边加砝码,直到天平平衡,于是此时砝码的总质量就是牛的质量(约翰不能把砝码放到奶牛的那边,因为奶牛不喜欢称体重,每当约翰把砝码放到她的蹄子底下,她就会尝试把砝码踢到约翰脸上)。

天平能承受的物体的质量不是无限的,当天平某一边物体的质量大于 C (1≤C≤230)时,天平就会被损坏。砝码按照它们质量的大小被排成一行。并且,这一行中从第 3 个砝码开始,每个砝码的质量至少等于前面两个砝码(也就是质量比它小的砝码中质量最大的两个)的质量的和。

约翰想知道,用他所拥有的这些砝码以及这架天平,能称出的质量最大是多少。由于天平的最大承重能力为 C,他不能把所有砝码都放到天平上。

现在约翰告诉你每个砝码的质量,以及天平能承受的最大质量,你的任务是选出一些砝码,使它们的质量和在不压坏天平的前提下是所有组合中最大的。

【输入】

\(1\) 行输入两个用空格隔开的正整数 \(N\)\(C\)

\(2\)\(N+1\) 行:每一行仅包含一个正整数,即某个砝码的质量。保证这些砝码的质量是一个不下降序列。

【输出】

输出一个正整数,表示用所给的砝码能称出的不压坏天平的最大质量。

【输入样例】

3 15
1
10
20

【输出样例】

11

【核心思想】

  1. 问题分析:给定 \(N\) 个砝码(质量不下降排列,且从第 \(3\) 个开始每个砝码质量 \(\geq\) 前两个之和)和天平最大承重 \(C\),需选出若干砝码使其总质量 \(\leq C\) 且尽可能大。这是一个带特殊性质的子集和问题,直接枚举 \(2^N\) 不可行,但砝码的斐波那契式增长特性允许强力剪枝。

  2. 算法选择

    • 深度优先搜索(DFS)+ 前缀和剪枝:从大到小枚举每个砝码的选/不选状态,利用前缀和进行可行性剪枝和最优性剪枝
    • 关键性质利用:由于 \(w_i \geq w_{i-1} + w_{i-2}\)(类斐波那契增长),前缀和 \(\text{sum}[i]\) 增长极快,使得搜索树深度有限,剪枝效率极高
  3. 关键步骤

    • 预处理:读取 \(N, C\) 和砝码质量 \(w[1..N]\),计算前缀和数组 \(\text{sum}[i] = \sum_{j=1}^{i} w_j\)
    • DFS递归(从第 \(n\) 个砝码递减到第 \(1\) 个):
      • 更新最优解:若当前已选总重量 \(\text{st}\) 满足 \(\text{ans} < \text{st} < C\),则更新 \(\text{ans} = \text{st}\)
      • 终止条件\(\text{dep} = 0\)(所有砝码处理完毕)
      • 分支枚举:对每个砝码 \(\text{dep}\),尝试选(\(a[\text{dep}] = 1\))或不选(\(a[\text{dep}] = 0\)
      • 可行性剪枝\(\text{st} + w[\text{dep}] \cdot a[\text{dep}] < C\)(选择后不超过容量)
      • 最优性剪枝\(\text{st} + \text{sum}[\text{dep}] > \text{ans}\)(即使剩余全部选入,仍有希望超过当前最优解)
      • 状态更新与回溯\(\text{st} \mathrel{+}= w[\text{dep}] \cdot a[\text{dep}]\),递归后 \(\text{st} \mathrel{-}= w[\text{dep}] \cdot a[\text{dep}]\)
    • 输出答案\(\text{ans}\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(2^N)\) 最坏情况,但由于类斐波那契增长性质,实际搜索树被大幅剪枝,效率接近 \(O(N)\)\(O(\log C)\)
    • 空间复杂度:\(O(N)\),递归栈深度和数组空间
  5. DFS + 前缀和剪枝的核心思想

    • 从大到小枚举:优先决策大砝码,快速逼近容量上限 \(C\),尽早获得较优解以加强后续剪枝
    • 可行性剪枝:若当前选择导致总重量 \(\geq C\),直接舍弃该分支(因为要求严格小于 \(C\)
    • 最优性剪枝:若当前已选重量加上剩余所有砝码重量之和 \(\leq \text{ans}\),则即使全选也无法超越当前最优,直接剪枝
    • 类斐波那契性质的关键作用\(w_i \geq w_{i-1} + w_{i-2}\) 保证前缀和呈指数增长,使得 \(\text{sum}[\text{dep}]\) 迅速超过 \(C - \text{st}\),大量分支被剪除
    • 适用于有特殊增长性质的子集选择、背包问题的精确求解

【算法标签】

普及 #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 55;        // 定义最大物品数量
int n;                    // 物品数量
int w[N];                 // 存储每个物品的重量
int c;                    // 背包容量
int a[N];                 // 记录每个物品的选择状态(0或1)
long int sum[N];          // 前缀和数组,sum[i]表示前i个物品的总重量
long int ans = 0;         // 存储最优解(最大不超过c的总重量)
int st = 0;               // 当前已选物品的总重量

// 深度优先搜索函数
// dep: 当前处理的物品编号(从n开始递减)
void DFS(int dep)
{
    // 更新最优解(如果当前解更优且未超过容量)
    if (ans < st && st < c)
        ans = st;

    // 终止条件:所有物品都已处理
    if (dep == 0)
        return;

    // 尝试两种选择:选或不选当前物品
    for (int i = 1; i <= 2; i++)
    {
        a[dep] = 2 - i;  // a[dep]=1表示选,a[dep]=0表示不选

        // 剪枝条件:
        // 1. 选择后不超过容量
        // 2. 剩余物品的总重量加上当前重量可能超过当前最优解
        if (st + w[dep] * a[dep] < c && st + sum[dep] > ans)
        {
            st += w[dep] * a[dep];  // 更新当前总重量
            DFS(dep - 1);           // 递归处理下一个物品
            st -= w[dep] * a[dep];  // 回溯,恢复总重量
        }
    }
    return;
}

int main()
{
    // 输入物品数量和背包容量
    cin >> n >> c;

    // 初始化前缀和数组
    sum[0] = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i];                // 输入每个物品的重量
        sum[i] = sum[i - 1] + w[i]; // 计算前缀和
    }

    // 从最后一个物品开始深度优先搜索
    DFS(n);

    // 输出最优解
    cout << ans << endl;

    return 0;
}

【运行结果】

3 15
1
10
20
11
posted @ 2026-08-29 09:38  团爸讲算法  阅读(4)  评论(0)    收藏  举报