题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛,每头奶牛有智商 \(S_i\) 和情商 \(F_i\)。选择若干头奶牛参展,要求选中的奶牛智商之和 \(\geq 0\) 且情商之和 \(\geq 0\)。目标是最大化智商之和与情商之和的总和。可以一头都不选,此时输出 \(0\)。这是一个01背包 + 偏移量处理负数问题。

  2. 算法选择

    • 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)\)
  3. 关键步骤

    • 初始化
      • 读入 \(N\) 和每头奶牛的 \(S_i\)\(F_i\)
      • memset(dp, -0x3f, sizeof(dp)):DP数组初始化为负无穷,表示不可达状态
      • dp[0][OFFSET] = 0:初始状态,智商之和为 \(0\)(对应下标 \(OFFSET\)),情商之和为 \(0\)
    • 状态转移(枚举每头奶牛 \(i\)\(1\)\(N\)):
      • cur = i & 1pre = (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\)
    • 统计答案
      • 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\),表示不选任何奶牛)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \times M)\),其中 \(M = 2 \times OFFSET = 800000\)\(N \leq 400\),总操作数约 \(3.2 \times 10^8\)
    • 空间复杂度:\(O(M)\),滚动数组仅需两层,每层 \(2 \times OFFSET + 1\) 个整数
  5. 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
posted @ 2026-06-25 19:14  团爸讲算法  阅读(8)  评论(0)    收藏  举报