题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 个砝码(质量不下降排列,且从第 \(3\) 个开始每个砝码质量 \(\geq\) 前两个之和)和天平最大承重 \(C\),需选出若干砝码使其总质量 \(\leq C\) 且尽可能大。这是一个带特殊性质的子集和问题,直接枚举 \(2^N\) 不可行,但砝码的斐波那契式增长特性允许强力剪枝。
-
算法选择:
- 深度优先搜索(DFS)+ 前缀和剪枝:从大到小枚举每个砝码的选/不选状态,利用前缀和进行可行性剪枝和最优性剪枝
- 关键性质利用:由于 \(w_i \geq w_{i-1} + w_{i-2}\)(类斐波那契增长),前缀和 \(\text{sum}[i]\) 增长极快,使得搜索树深度有限,剪枝效率极高
-
关键步骤:
- 预处理:读取 \(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}\)
-
时间/空间复杂度:
- 时间复杂度:\(O(2^N)\) 最坏情况,但由于类斐波那契增长性质,实际搜索树被大幅剪枝,效率接近 \(O(N)\) 或 \(O(\log C)\)
- 空间复杂度:\(O(N)\),递归栈深度和数组空间
-
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
浙公网安备 33010602011771号