题解:洛谷 B4071 武器强化

【题目来源】

洛谷:B4071 [GESP202412 五级] 武器强化 - 洛谷

【题目描述】

小杨有 \(n\) 种武器和 \(m\) 种强化材料。第 \(i\) 种强化材料会适配第 \(p_i\) 种武器,小杨可以花费 \(c_i\) 金币将该材料对应的适配武器修改为任意武器。

小杨最喜欢第 \(1\) 种武器,因此他希望适配该武器的强化材料种类数严格大于其他的武器,请你帮小杨计算为了满足该条件最少需要花费多少金币。

【输入】

小杨有 \(n\) 种武器和 \(m\) 种强化材料。第 \(i\) 种强化材料会适配第 \(p_i\) 种武器,小杨可以花费 \(c_i\) 金币将该材料对应的适配武器修改为任意武器。

小杨最喜欢第 \(1\) 种武器,因此他希望适配该武器的强化材料种类数严格大于其他的武器,请你帮小杨计算为了满足该条件最少需要花费多少金币。

【输出】

输出一个整数,代表能够使适配第 \(1\) 种武器的强化材料种类数严格大于其他的武器最少需要花费的金币。

【输入样例】

4 4
1 1
2 1
3 1
3 2

【输出样例】

1

【核心思想】

  1. 问题分析:给定 \(n\) 种武器、\(m\) 种强化材料,第 \(i\) 种材料适配武器 \(p_i\),修改花费 \(c_i\)。目标是让武器 \(1\) 的适配材料数严格大于其他所有武器,求最小花费。这是一个枚举目标票数 + 贪心选取问题,关键在于枚举武器 \(1\) 的目标票数 \(x\),然后贪心地将其他武器的材料"压制"到 \(< x\)

  2. 算法选择

    • 枚举目标票数 \(x\):武器 \(1\) 最终需要有 \(x\) 票(\(x \geq \max(a[1].size(), 1)\)\(x \leq m\)
    • 贪心压制策略:对每个其他武器 \(i\),若其当前票数 \(\geq x\),必须拿走至少 a[i].size() - x + 1 张票(使其票数 \(\leq x-1\)),优先拿走花费最小的票
    • 补票策略:若压制后武器 \(1\) 的票数仍不足 \(x\),从所有剩余可购买的票中选花费最小的补足
  3. 关键步骤

    • 读入与排序:读入 \(n, m\)\(m\)\((p_i, c_i)\),按武器分类存入 a[p],并对每个 a[i] 按花费升序排序
    • 枚举 \(x\)(从 max(a[1].size(), 1)\(m\)):
      • cnt = x - a[1].size():武器 \(1\) 还需要获得的票数
      • res = 0:当前总花费
      • 压制其他武器\(i\)\(2\)\(n\)):
        • sz = max(0, a[i].size() - x + 1):需要从武器 \(i\) 拿走的最少票数
        • 拿走 a[i] 中最便宜的 sz 张:res += sum(a[i][0..sz-1])cnt -= sz
        • res >= ans 提前剪枝返回
        • a[i] 中剩余的票加入 temp(可选购买列表)
      • 补票:若 cnt > 0,将 temp 排序,取最便宜的 cnt
      • ans = min(ans, res)
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \log m + n \cdot m \log m)\),排序 \(O(m \log m)\),枚举 \(x\) 最多 \(m\) 次,每次处理 \(O(n + m \log m)\)
    • 空间复杂度:\(O(m)\),存储所有材料花费
  5. 枚举 + 贪心压制的核心思想

    • 严格大于的转化:武器 \(1\) 票数 $> $ 武器 \(i\) 票数 \(\Leftrightarrow\) 武器 \(i\) 票数 \(\leq x-1\),其中 \(x\) 为武器 \(1\) 的目标票数
    • 最小压制成本:对每个其他武器,使其票数 \(\leq x-1\) 的最小成本是拿走其最便宜的 size - (x-1) 张票
    • 补票的池化:压制过程中未拿走的票和武器 \(1\) 原有的票构成"可购买池",从中选最便宜的补足差额
    • 枚举保证最优\(x\) 的取值范围有限(最多 \(m\) 种),枚举所有可能并取最小值保证全局最优
    • 适用于"使某元素严格最大化的最小代价"问题,核心在于枚举目标值 + 贪心压制其他元素

【算法标签】

普及 #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1005;

int n, m, p, c, ans = 1e18;
vector<int> a[N];

// 计算让1号孩子获得x票的最小花费
int f(int x) {
    int cnt = x - a[1].size(); // 需要从其他孩子那里获取的票数
    vector<int> temp; // 存储可以购买的武器
    int res = 0; // 总花费

    // 处理其他孩子
    for (int i = 2; i <= n; i++) {
        if (a[i].empty()) continue;

        // 计算需要从这个孩子拿走多少武器
        int sz = max(0LL, (long long)(a[i].size() - x + 1));

        // 拿走最便宜的sz个武器
        for (int j = 0; j < sz; j++) {
            res += a[i][j];
            cnt--;
        }

        // 如果已经超过当前最优解,提前返回
        if (res >= ans) return ans;

        // 剩余的武器加入可选列表
        for (int j = sz; j < a[i].size(); j++) {
            temp.push_back(a[i][j]);
        }
    }

    // 如果还需要更多票,从剩余的武器中选择最便宜的
    if (cnt > 0) {
        sort(temp.begin(), temp.end());
        for (int i = 0; i < cnt && i < temp.size(); i++) {
            res += temp[i];
        }
    }

    return res;
}

signed main() {
    cin >> n >> m;

    // 读入数据并排序
    for (int i = 1; i <= m; i++) {
        cin >> p >> c;
        a[p].push_back(c);
    }

    // 对每个孩子的武器按价格排序
    for (int i = 1; i <= n; i++) {
        sort(a[i].begin(), a[i].end());
    }

    // 尝试让1号孩子获得从当前票数到总票数的各种可能
    int min_votes = max((long long)a[1].size(), 1LL);
    for (int i = min_votes; i <= m; i++) {
        ans = min(ans, f(i));
    }

    cout << ans << endl;
    return 0;
}

【运行结果】

4 4
1 1
2 1
3 1
3 2
1
posted @ 2026-08-10 12:24  团爸讲算法  阅读(30)  评论(0)    收藏  举报