题解:B4071 [GESP202412 五级] 武器强化
本题解注重以下 3 个问题:
- 本题的基本思路是什么
- 解题的逻辑和步骤
- 更多的启发
基本思路
本题的对于贪心来说,最大的困扰是:你究竟是选择最低价的配件来转换,还是选择那些拥有最多配件的其他武器来转换。选择最低价的好处当然是成本低,但优先选择目前排名最高的武器的配件,一方面增加了自己,一方面可以打击对手,有双重的好处。
看似这里应该做一个动态规划,但是复杂度太高,仍然应该用贪心的思路来做。贪心题的一般解法步骤是:
- 把问题分解为若干步
- 每步选择一个局部最优的方案
- 把这些方案合并期望得到全局最优解
分解步骤是最关键的,如果这道题你把步骤分解为每次选择一个配件,让后看怎样找到最合适的配件,这就无法成功(由于上面的困扰)。
正确的做法是进行多轮游戏:在每轮游戏中使用不同的策略,最后在所有策略中找到一个最好的策略。下面展开说明一下策略的设计。
解题的逻辑和步骤
这里的重点多轮游戏怎么设计,用了一种反向思维,从结果倒推过程。回答两个问题:即 1 号武器赢的时候究竟拥有多少配件?它究竟是怎样赢的?
首先, 1 号武器必须超过所有其他武器,因为配件总数是m,所以 1 号最多占有
现在,看它是怎么赢的。假设 1 号武器赢的时候拥有 x 个配件,那么所有武器最多保留
具体做法如下就是枚举所有赢时的配件数量,然后用上面说的这个逻辑去倒推计算实际的费用。最后在所有情况中选择一个费用最低的方案。我们同时得到了最低费用和此时的 1 号武器配件数。
更多的启发
当明确了取胜的配件数量时,这个具体的计算过程,确实的贪心。但实际上获得最优解靠的不是贪心而是枚举。 如果数量特别大,还可以用二分。
贪心关注的是局部,但最优解必然事实上是全局最优的。所以我们的解法中,必然会在某个角度反映出全局的考虑。这种贪心+枚举(或二分)的技巧在实际题目中还是比较经常出现的。
AC 代码如下
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m;
vector<int> cs[1003];
// 计算费用的核心函数
ll calc(int aim) {
int cur_cnt = cs[1].size();
ll res = 0;
vector<int> tmp; // 备选配件库
// 首先,从2号开始,每种武器超过aim-1的部分,全部转换,必不可少的费用
for (int i = 2; i<=n; i++) {
int buy = max((int)cs[i].size() - aim + 1, 0);
for (int j = 0; j < buy; j++) {
res += cs[i][j];
}
cur_cnt += buy;
// 剩下的配件,进入配件库备选
for (int j = buy; j < cs[i].size(); ++j) {
tmp.push_back(cs[i][j]);
}
}
sort(tmp.begin(), tmp.end()); // 备选配件库排序
// 收了所有武器的超标配件后,1号可能仍然达不到获胜标准,再选最低价
for (int i = 0; i < aim - cur_cnt; i++) {
res += tmp[i];
}
return res;
}
int main() {
cin >> n >> m;
for (int i = 1; i <=m; i++) {
int p, c;
cin >> p >> c;
cs[p].push_back(c);
}
// 每种武器的配件按费用排序,因为会从最低价格开始使用
for (int i = 1; i <=n; i++) {
sort(cs[i].begin(), cs[i].end());
}
ll ans = 1e18;
// 遍历1号武器胜利时的数量,并计算费用
for (int i = 1; i <= m/2+1; i++) {
ll x= calc(i);
ans = min(ans, x);
}
cout << ans;
}