题解:洛谷 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
【核心思想】
-
问题分析:给定 \(n\) 种武器、\(m\) 种强化材料,第 \(i\) 种材料适配武器 \(p_i\),修改花费 \(c_i\)。目标是让武器 \(1\) 的适配材料数严格大于其他所有武器,求最小花费。这是一个枚举目标票数 + 贪心选取问题,关键在于枚举武器 \(1\) 的目标票数 \(x\),然后贪心地将其他武器的材料"压制"到 \(< x\)。
-
算法选择:
- 枚举目标票数 \(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\),从所有剩余可购买的票中选花费最小的补足
-
关键步骤:
- 读入与排序:读入 \(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
- 读入与排序:读入 \(n, m\) 和 \(m\) 对 \((p_i, c_i)\),按武器分类存入
-
时间/空间复杂度:
- 时间复杂度:\(O(m \log m + n \cdot m \log m)\),排序 \(O(m \log m)\),枚举 \(x\) 最多 \(m\) 次,每次处理 \(O(n + m \log m)\)
- 空间复杂度:\(O(m)\),存储所有材料花费
-
枚举 + 贪心压制的核心思想:
- 严格大于的转化:武器 \(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
浙公网安备 33010602011771号