把有效攻击看成强牌匹配弱牌,排序后用双指针求最多能击败多少只怪兽。
OJ: luogu
题目 ID: P11231
难度:普及/提高-
标签:贪心排序
日期: 2026-06-22 18:23
题意
有 n 只怪兽,第 i 只怪兽的攻击力和防御力都等于 r_i。
每回合可以选择一只还在场的怪兽 i,让它攻击另一只还在场的怪兽 j。如果 r_i > r_j,则 j 退出游戏;否则无事发生。每只怪兽整场游戏中至多发起一次攻击。
要求安排攻击顺序,使最后剩余怪兽数量最少。
思路
先看一个小数据暴力:用 alive 记录还活着的怪兽,用 used 记录已经攻击过的怪兽,递归枚举下一次攻击的攻击者和目标。
cpp
// brute.cpp:小数据搜索所有攻击顺序,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 12;
int n;
int r[MAXN];
map<long long, int> memo;
int count_bits(int x) {
int cnt = 0;
while (x > 0) {
cnt += x & 1;
x >>= 1;
}
return cnt;
}
int dfs(int alive, int used) {
long long key = ((long long)alive << n) | used;
auto it = memo.find(key);
if (it != memo.end()) {
return it->second;
}
int can_attack = 0;
for (int i = 0; i < n; i++) {
if ((alive & (1 << i)) && !(used & (1 << i))) {
can_attack = 1;
}
}
if (!can_attack) {
int ret = count_bits(alive);
memo[key] = ret;
return ret;
}
int ans = count_bits(alive);
for (int i = 0; i < n; i++) {
if (!(alive & (1 << i)) || (used & (1 << i))) {
continue;
}
for (int j = 0; j < n; j++) {
if (i == j || !(alive & (1 << j))) {
continue;
}
int next_alive = alive;
int next_used = used | (1 << i);
if (r[i] > r[j]) {
next_alive &= ~(1 << j);
}
ans = min(ans, dfs(next_alive, next_used));
}
}
memo[key] = ans;
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> r[i];
}
memo.clear();
cout << dfs((1 << n) - 1, 0) << '\n';
return 0;
}暴力的状态数是指数级的,无法处理 n=10^5。
关键点是:一只怪兽可以先攻击别人,之后再被更强的怪兽击败。因此我们不需要纠结具体攻击顺序,可以把一次有效攻击看成一对匹配:
text
攻击者的 r > 被击败者的 r也就是说,我们有两份相同的怪兽集合:一份作为攻击者,一份作为被击败者。每张牌在攻击者侧最多用一次,在被击败者侧最多用一次。目标是最多配出多少对 r_attacker > r_victim。
排序后用双指针贪心即可:
- 把所有
r_i从小到大排序; victim指向当前最弱的待击败怪兽;attacker从小到大扫描攻击者;- 如果
r[attacker] > r[victim],就用它击败victim,匹配数加一; - 否则这个攻击者连最弱目标都打不过,只能跳过。
为什么这样是对的?如果当前攻击者打不过最弱目标,它也打不过任何更强目标,跳过不会损失答案。如果当前攻击者能打过最弱目标,用这个“最弱可行攻击者”去击败当前最弱目标最省强牌,不会比用更强怪兽更差。
设最大可击败数为 killed,那么最少剩余怪兽数就是:
text
n - killed代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
int r[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> r[i];
}
sort(r + 1, r + n + 1);
int victim = 1;
int attacker = 1;
int killed = 0;
// 给每个当前最弱的目标,找一个尽量弱但严格更强的攻击者。
while (victim <= n && attacker <= n) {
if (r[attacker] > r[victim]) {
killed++;
victim++;
attacker++;
} else {
attacker++;
}
}
cout << n - killed << '\n';
return 0;
}复杂度
排序复杂度为
空间复杂度为
总结
这题容易被“攻击顺序”干扰。真正重要的是:怪兽可以先攻击再被击败,所以一次有效攻击只是在“攻击者拷贝”和“被击败者拷贝”之间建立一对严格大于关系。
把问题转成最大匹配后,排序加双指针就能得到最多能击败多少只怪兽。