题解:洛谷 P2340 Cow Exhibition
【题目来源】
洛谷:P2340 [USACO03FALL] Cow Exhibition G - 洛谷
【题目描述】
奶牛想证明它们是聪明而风趣的。为此,贝西筹备了一个奶牛博览会,她已经对 \(N\) 头奶牛进行了面试,确定了每头奶牛的智商和情商。
贝西有权选择让哪些奶牛参加展览。由于负的智商或情商会造成负面效果,所以贝西不希望出展奶牛的智商之和小于零,或情商之和小于零。满足这两个条件下,她希望出展奶牛的智商与情商之和越大越好,请帮助贝西求出这个最大值。
【输入】
第一行:单个整数 \(N\),\(1 \le N \le 400\)。
第二行到第 \(N+1\) 行:第 \(i+1\) 行有两个整数:\(S_i\) 和 \(F_i\),表示第 \(i\) 头奶牛的智商和情商,\(-1000 \le S_i,F_i \le 1000\)。
【输出】
输出单个整数:表示情商与智商和的最大值。贝西可以不让任何奶牛参加展览,如果这样做是最好的,输出 \(0\)。
【输入样例】
5
-5 7
8 -6
6 -3
2 1
-8 -5
【输出样例】
8
【核心思想】
-
问题分析:给定 \(N\) 头奶牛,每头奶牛有智商 \(S_i\) 和情商 \(F_i\)。选择若干头奶牛参展,要求选中的奶牛智商之和 \(\geq 0\) 且情商之和 \(\geq 0\)。目标是最大化智商之和与情商之和的总和。可以一头都不选,此时输出 \(0\)。这是一个01背包 + 偏移量处理负数问题。
-
算法选择:
- 01背包(0-1 Knapsack):每头奶牛只有"选"或"不选"两种状态,符合01背包模型
- 偏移量技巧(Offset Technique):由于智商 \(S_i\) 可能为负数(\(-1000 \leq S_i \leq 1000\)),直接使用数组下标无法表示负数和。引入偏移量 \(OFFSET = 400000\),将智商之和为 \(x\) 的状态映射到数组下标 \(j = x + OFFSET\)
- 滚动数组优化:使用
dp[2][...]交替存储当前层和上一层状态,将空间复杂度从 \(O(N \times M)\) 优化至 \(O(M)\)
-
关键步骤:
- 初始化:
- 读入 \(N\) 和每头奶牛的 \(S_i\)、\(F_i\)
memset(dp, -0x3f, sizeof(dp)):DP数组初始化为负无穷,表示不可达状态dp[0][OFFSET] = 0:初始状态,智商之和为 \(0\)(对应下标 \(OFFSET\)),情商之和为 \(0\)
- 状态转移(枚举每头奶牛 \(i\) 从 \(1\) 到 \(N\)):
cur = i & 1,pre = (i-1) & 1:滚动数组切换- 枚举所有可能的智商之和下标 \(j\)(从 \(0\) 到 \(2 \times OFFSET\)):
- 不选第 \(i\) 头:
dp[cur][j] = dp[pre][j](继承上一层状态) - 选第 \(i\) 头(若 \(j \geq S_i\) 且 \(j - S_i\) 在范围内):
dp[cur][j] = max(dp[cur][j], dp[pre][j - S_i] + F_i)- 含义:从上一层智商之和为 \((j - S_i) - OFFSET\) 的状态转移,加上当前奶牛的情商 \(F_i\)
- 不选第 \(i\) 头:
- 统计答案:
last = n & 1:最终状态所在层- 遍历 \(j\) 从 \(OFFSET\) 到 \(2 \times OFFSET\)(只考虑智商之和 \(\geq 0\) 的状态):
- 若
dp[last][j] >= 0(情商之和也 \(\geq 0\),满足两个约束):sumS = j - OFFSET(实际智商之和)sumF = dp[last][j](实际情商之和)ans = max(ans, sumS + sumF)
- 若
- 输出 \(ans\)(注意 \(ans\) 初始为 \(0\),表示不选任何奶牛)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times M)\),其中 \(M = 2 \times OFFSET = 800000\)。\(N \leq 400\),总操作数约 \(3.2 \times 10^8\)
- 空间复杂度:\(O(M)\),滚动数组仅需两层,每层 \(2 \times OFFSET + 1\) 个整数
-
01背包 + 偏移量处理负权的核心思想:
- 双约束转化:将"智商之和 \(\geq 0\) 且情商之和 \(\geq 0\)"的约束,转化为以智商之和作为DP维度、情商之和作为DP值,最后同时检查两个约束
- 偏移量技巧:当背包容量/重量可能为负时,引入足够大的偏移量将所有可能值映射到非负下标。本题中 \(N \times |S_{max}| = 400 \times 1000 = 400000\),取 \(OFFSET = 400000\) 可覆盖 \([-400000, 400000]\) 的范围
- DP值含义:
dp[j]表示智商之和为 \(j - OFFSET\) 时,情商之和的最大值。这与传统01背包"价值最大化"不同,这里是将情商作为被优化的"价值" - 初始化为负无穷:不可达状态设为负无穷,只有从
dp[OFFSET] = 0出发能到达的状态才有效。最后检查dp[j] >= 0既保证了情商之和 \(\geq 0\),又过滤了不可达状态 - 适用于带负权、双属性约束、多维01背包类问题
【算法标签】
普及 #01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 405, M = 400005, OFFSET = 400000; // N: 最大奶牛数, M: DP数组范围, OFFSET: 偏移量(处理负数)
int n; // 奶牛数量
int s[N], f[N]; // s[i]: 第i头奶牛的智商, f[i]: 第i头奶牛的情商
// dp[cur][j]: 当前处理到第i头奶牛,智商之和为(j-OFFSET)时,情商之和的最大值
// 使用滚动数组优化空间:cur和pre交替使用
int dp[2][2 * M];
int main()
{
cin >> n; // 读入奶牛数量
// 读入每头奶牛的智商和情商
for (int i = 1; i <= n; i++)
{
cin >> s[i] >> f[i];
}
// 初始化DP数组为负无穷(表示不可达状态)
memset(dp, -0x3f, sizeof(dp));
// 初始状态:一头奶牛都不选时,智商之和为0,情商之和为0
// 用偏移量OFFSET表示智商之和为0:dp[0][OFFSET] = 0
dp[0][OFFSET] = 0;
// 动态规划:枚举每头奶牛
for (int i = 1; i <= n; i++)
{
int cur = i & 1; // 当前层下标(0或1,滚动数组)
int pre = (i - 1) & 1; // 上一层下标
// 枚举所有可能的智商之和(用偏移后的下标j表示)
for (int j = 0; j <= 2 * M; j++)
{
// 情况1:不选第i头奶牛,继承上一层的值
dp[cur][j] = dp[pre][j];
// 情况2:选第i头奶牛
// j >= s[i] + OFFSET 等价于 当前智商之和 >= 选择后的智商之和
// 注意:s[i]可能是负数,所以用j-s[i]判断是否在范围内
if (j >= s[i] && j - s[i] <= 2 * M)
{
// 从上一层的(j-s[i])状态转移过来,加上当前奶牛的情商f[i]
dp[cur][j] = max(dp[cur][j], dp[pre][j - s[i]] + f[i]);
}
}
}
// 统计答案:在所有满足条件的方案中,找智商与情商之和的最大值
int ans = 0; // 可以不选任何奶牛,所以ans至少为0
int last = n & 1; // 最终状态所在的层
// 只考虑智商之和 >= 0 的状态(即j >= OFFSET)
for (int j = OFFSET; j <= 2 * M; j++)
{
// dp[last][j] >= 0 表示该状态下情商之和也 >= 0(满足两个约束条件)
if (dp[last][j] >= 0)
{
int sumS = j - OFFSET; // 实际的智商之和
int sumF = dp[last][j]; // 实际的情商之和
ans = max(ans, sumS + sumF); // 更新最大值
}
}
cout << ans << endl; // 输出最大智商与情商之和
return 0;
}
【运行结果】
5
-5 7
8 -6
6 -3
2 1
-8 -5
8
浙公网安备 33010602011771号