P4377 [USACO18OPEN] Talent Show G|01 分数规划 + 01 背包限制重量下界 完整题解
一、题目概述
题意翻译
有 $n$ 头奶牛,每头奶牛拥有重量 $w_i$、才艺值 $t_i$,选出一组奶牛满足两个条件:
- 所选奶牛总重量 $\ge W$
- 最大化比值 $\frac{\sum t_i}{\sum w_i}$(总才艺 / 总重量)
输出要求:将最优比值 $\times 1000$ 后向下取整输出整数。
补充提示
浮点精度误差会导致本该为整数的结果算成略小的值,输出前需要加极小偏移避免取整出错。
样例解读
输入:
3 15
20 21
10 11
30 31
可选组合:
- 选第 1 头和第 2 头:重量 $10+20=30 \ge 15$,总才艺 $21+11=32$
- 比值 $32/30 \approx 1.06666$
- $1.06666 \times 1000 = 1066.66$,向下取整输出 $1066$,与样例输出匹配
数据模型定位
- 分式最大化:标准 01 分数规划,使用浮点二分
- 约束条件:总重量至少 $W$,不是恰好 $W$,需要改造 01 背包处理下界限制
二、核心数学推导(分数规划)
我们二分猜测比值 $x$,目标判断是否存在合法选牛方案满足:
$$
\frac{\sum t_i}{\sum w_i} \ge x
$$
两边同乘正数 $\sum w_i$,变形得到判定不等式:
$$
\sum t_i - x \cdot \sum w_i \ge 0
\Rightarrow \sum (t_i - x \cdot w_i) \ge 0
$$
令每头牛的价值为 $val_i = t_i - x \cdot w_i$,问题转化:
选出若干奶牛,总重量 $\ge W$,使 $\sum val_i$ 尽可能大,判断最大值是否 $\ge 0$
- 最大值 $\ge 0$:说明存在方案比值 $\ge x$,可以尝试更大的 $x$
- 最大值 $< 0$:当前 $x$ 过高,无法达到,需要缩小 $x$
单调性成立,因此可以使用浮点二分逼近最优解。
三、特殊背包处理:总重量 $\ge W$
常规 01 背包只能处理「总重量不超过 $W$」,本题要求总重量至少 $W$,优化思路:
- 背包数组 $f[j]$ 定义:总重量恰好为 $j$ 时,$\sum val_i$ 的最大值
- 对于重量超过 $W$ 的所有情况,统一合并存入 $f[W]$:
- 即当 $j + w_i > W$ 时,下标直接取 $W$,不再记录更大重量
- 最终只需要看 $f[W]$ 的最大值:代表所有总重量 $\ge W$ 的组合里,$\sum val_i$ 的最优值
这样背包容量只需要开到 $W$,大幅降低时间、空间开销。
背包转移公式
遍历每一头奶牛,倒序枚举容量(01 背包防重复选取):
$$
k = \min(W, j + w_i)
$$
$$
f[k] = \max(f[k], f[j] + t_i - x \cdot w_i)
$$
四、完整 AC 代码
#include <bits/stdc++.h>
using namespace std;
int n, W, w[255], t[255];
double f[1005];
bool check(double x) {
// 初始化负无穷,表示不可达
for(int i = 1; i <= W; i++) f[i] = -1e9;
f[0] = 0;
for(int i = 1; i <= n; i++) {
// 01背包倒序枚举
for(int j = W; j >= 0; j--) {
if(f[j] == -1e9) continue;
int k = min(W, j + w[i]);
f[k] = max(f[k], f[j] + t[i] - x * w[i]);
}
}
return f[W] > 0;
}
double find() {
double l = 0, r = 1000;
// 精度1e-5,满足×1000后取整无误差
while(r - l > 1e-5) {
double mid = (l + r) / 2;
if(check(mid)) l = mid; // 当前x可行,放大左边界
else r = mid;
}
return r;
}
int main() {
scanf("%d%d", &n, &W);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &w[i], &t[i]);
}
// +1e-8 规避浮点精度向下取整bug
printf("%d\n", int(find() * 1000 + 1e-8));
return 0;
}
五、代码逐段分步讲解
1. 全局变量定义
int n, W, w[255], t[255];
double f[1005];
- $n \le 250$:奶牛数量上限,数组开 255 足够
- $W \le 1000$:重量下界限制,背包容量数组 $f$ 开到 1005
double f[]:存储对应重量下 $\sum (t_i - x \cdot w_i)$ 的最大值,浮点型适配二分的 $x$
2. check(x) 二分判定函数(核心背包)
bool check(double x) {
for(int i = 1; i <= W; i++) f[i] = -1e9;
f[0] = 0; // 重量0,不选任何牛,总和为0
for(int i = 1; i <= n; i++) {
// 01背包倒序,避免重复选取同一头牛
for(int j = W; j >= 0; j--) {
if(f[j] == -1e9) continue;
int k = min(W, j + w[i]);
f[k] = max(f[k], f[j] + t[i] - x * w[i]);
}
}
// f[W]存储所有总重量≥W的最优总和
return f[W] > 0;
}
执行流程:
- 初始化数组为极小值,仅 $f[0] = 0$(初始状态:0 重量 0 收益)
- 枚举每一头奶牛,倒序遍历背包容量
- 新重量超过 $W$ 则压缩到下标 $W$,更新该位置最大收益
- 最终判断:所有总重量 $\ge W$ 的组合最大收益是否大于 0
3. find() 浮点二分主逻辑
double find() {
double l = 0, r = 1000;
while(r - l > 1e-5) {
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
return r;
}
- 二分区间 $[0, 1000]$:才艺与重量比值最大不超过 1000
- 精度阈值 $1e-5$:最终要放大 1000 倍,该精度可以保证小数点后三位准确
- 分支逻辑:
check(mid) = true:存在方案满足比值 $\ge mid$,更新左边界,尝试更大比值check(mid) = false:$mid$ 过大,缩小右边界
4. main 输入输出与精度坑处理
int main() {
scanf("%d%d", &n, &W);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &w[i], &t[i]);
}
// +1e-8 解决浮点向下取整误差
printf("%d\n", int(find() * 1000 + 1e-8));
return 0;
}
重点细节:find() * 1000 + 1e-8
题目提示的精度 bug 解决方案:由于浮点存储存在微小误差,真实值 1.066 会被计算为 1.0659999999,直接转 int 会变成 1065;加上极小值 $10^{-8}$ 后修正误差,向下取整结果正确。
六、算法时间复杂度分析
- 浮点二分:循环约 50 次(区间 0~1000,精度 $1e-5$)
- 单次背包:$O(n \times W)$,$n \le 250, W \le 1000$,单次仅 25 万次运算
- 总复杂度:$O(50 \times n \times W)$,数据范围下完全无超时风险
七、题型拆解与同类对比
1. 01 分数规划两类经典约束
- 固定选 k 件:POJ2976 Dropping tests → 排序贪心取前 n-k 项
- 重量下界 / 上界约束:本题 Talent Show → 结合 01 背包求解最值
2. 背包变形技巧拓展
- 重量上限限制:标准 01 背包,容量 $W$,只存 $\le W$ 的重量
- 重量下限限制:本题压缩超过 $W$ 的重量统一存入 $f[W]$,节省空间与计算量
- 同时有上下界:分段背包分别处理
八、踩坑避坑总结
- 背包初始化:必须设为负无穷,不能初始 0,否则会误判不可达状态
- 倒序遍历背包:01 背包标准要求,正序会变成完全背包,重复选牛
- 重量压缩到 $W$:不能直接开更大数组,否则空间超时
- 浮点精度取整:输出前必须加极小偏移值,否则样例 / 边界数据会 WA
- 二分分支方向:本题最大化比值,可行时更新左边界 $l = mid$,不要写反
九、全文总结
模型核心
01 分数规划 + 变形 01 背包,是提高组二分 + DP 综合经典题
解题三步固定流程
- 数学变形分式:构造 $val_i = t_i - x \cdot w_i$
- 浮点二分猜测比值 $x$
- 改造 01 背包处理「总重量 $\ge W$」约束,判断最优收益是否 $\ge 0$
应试价值
融合二分、动态规划、浮点精度三大考点,是 NOIP/CSP 提高组高频综合题型,熟练掌握可打通分数规划 + 背包类所有题目。
浙公网安备 33010602011771号