稳定病毒只可能是本细胞最强病毒;可行病毒则枚举终点细胞,判断所有更强病毒能否被更弱的安全病毒逐个消灭。
OJ: luogu
题目 ID: P9287
难度:提高+/省选-
标签:思维构造
日期: 2026-06-19 02:18
题意
有 n 个细胞、n 个病毒。
初始时第 i 个细胞感染第 i 个病毒。每个细胞对所有病毒都有一套从强到弱的排名。
如果一个感染了病毒 x 的细胞攻击另一个当前感染病毒 y 的细胞 j,并且细胞 j 认为 x 比 y 更强,那么细胞 j 就会改感染 x。
所有攻击顺序都可以任意安排,直到再也没有感染能发生为止。
输入最后一个参数 p:
p = 1:要求输出所有稳定病毒,也就是无论怎么安排攻击顺序,最终都一定不会灭绝的病毒。p = 2:要求输出所有可行病毒,也就是至少存在一种攻击顺序,使它最终不会灭绝的病毒。
思路
先看最直接的做法:把当前每个细胞感染的病毒都维护下来,然后暴力枚举所有可能的攻击顺序,搜索所有终局:
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:小数据暴力解。
// 直接搜索所有可能的感染过程,收集所有终局,再统计稳定 / 可行病毒。
const int MAXN = 8;
int n, p;
int a[MAXN][MAXN];
int rk[MAXN][MAXN];
vector<vector<int>> states;
map<vector<int>, int> id_of;
queue<int> que;
vector<vector<int>> terminal_states;
int get_id(const vector<int> &state) {
map<vector<int>, int>::iterator it = id_of.find(state);
if (it != id_of.end()) {
return it->second;
}
int id = (int)states.size();
states.push_back(state);
id_of[state] = id;
que.push(id);
return id;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
for (int k = 1; k <= n; k++) {
cin >> a[i][k];
rk[i][a[i][k]] = k;
}
}
cin >> p;
vector<int> start(n + 1);
for (int i = 1; i <= n; i++) {
start[i] = i;
}
get_id(start);
while (!que.empty()) {
int id = que.front();
que.pop();
vector<int> cur = states[id];
bool moved = false;
for (int from = 1; from <= n; from++) {
int virus = cur[from];
for (int to = 1; to <= n; to++) {
if (from == to) {
continue;
}
if (rk[to][virus] < rk[to][cur[to]]) {
moved = true;
vector<int> nxt = cur;
nxt[to] = virus;
get_id(nxt);
}
}
}
if (!moved) {
terminal_states.push_back(cur);
}
}
vector<int> feasible(n + 1, 0);
vector<int> stable(n + 1, 1);
for (int idx = 0; idx < (int)terminal_states.size(); idx++) {
vector<int> used(n + 1, 0);
for (int i = 1; i <= n; i++) {
used[terminal_states[idx][i]] = 1;
}
for (int v = 1; v <= n; v++) {
if (used[v]) {
feasible[v] = 1;
} else {
stable[v] = 0;
}
}
}
vector<int> ans;
if (p == 1) {
for (int v = 1; v <= n; v++) {
if (stable[v]) {
ans.push_back(v);
}
}
} else {
for (int v = 1; v <= n; v++) {
if (feasible[v]) {
ans.push_back(v);
}
}
}
cout << ans.size() << '\n';
for (int i = 0; i < (int)ans.size(); i++) {
if (i) {
cout << ' ';
}
cout << ans[i];
}
cout << '\n';
return 0;
}这个暴力只能做很小的数据,但它能帮助我们看清题目结构。
稳定病毒
先处理 p = 1。
如果病毒 i 在细胞 i 心中就是最强的,那么细胞 i 永远不可能被别的病毒感染,因此病毒 i 一定不会灭绝。
反过来,如果病毒 i 不是细胞 i 心中的最强病毒,那么总存在某个更强病毒可以在合适时机感染细胞 i,于是病毒 i 不一定能保住。
所以稳定病毒的判定非常简单:
a[i][1] == i
也就是在细胞 i 的强弱排名里,第 1 名恰好是病毒 i。
可行病毒
再处理 p = 2。
枚举一个病毒 i,思考它能不能在某个终局里活下来。
如果它能活下来,那么终局里它一定至少占据某个细胞 j。于是我们继续枚举这个目标细胞 j。
要让终局时细胞 j 感染病毒 i,首先必须满足:
病毒 i 对细胞 j 来说,至少不比初始病毒 j 更弱。
否则从头到尾,病毒 i 都没有机会感染到 j。
接下来考虑哪些病毒会阻止这个终局成立。
如果某个病毒 x 在细胞 j 心中比病毒 i 更强,而且终局时它还活着,那么任意一个感染 x 的细胞都还能继续攻击 j,说明这就不是终局。
因此,所有在细胞 j 心中比 i 更强的病毒,都必须能够被消灭。
一个更强病毒怎么被消灭
固定这样一个更强病毒 x。
想消灭它,只需要找到某个病毒 y:
- 在细胞
j心中,y不能比i更强。
否则就算y帮我们消灭了x,它自己也会继续威胁j。 - 在细胞
x心中,y要比x更强。
这样y才能去感染细胞x,把病毒x杀掉。
所以对每个 (i, j),我们只要检查:
- 在细胞
j心中所有比i更强的病毒x - 是否都存在一个“安全病毒”
y,满足y在细胞j心中不比i更强y在细胞x心中比x更强
如果每个这样的 x 都能被某个安全病毒消灭,那么病毒 i 就可以在细胞 j 活下来。
如何优化
为了快速判断“是否存在这样的安全病毒 y”,我们预处理:
best[j][x] = 在细胞 x 心中比 x 更强的所有病毒里,它们在细胞 j 心中的最靠后排名
如果 best[j][x] >= rank[j][i],说明确实存在一个病毒 y:
- 它在细胞
x心中比x强; - 它在细胞
j心中排在i后面或就是i,因此是安全的。
这样枚举病毒 i、目标细胞 j、再检查所有更强病毒 x 即可,总复杂度
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 505;
int n, p;
int a[MAXN][MAXN]; // a[i][k] 表示在细胞 i 心中第 k 强的病毒编号
int rk[MAXN][MAXN]; // rk[i][v] 表示病毒 v 在细胞 i 心中的排名,1 表示最强
int best_pos[MAXN][MAXN]; // best_pos[j][x]:在细胞 x 心中比 x 强的病毒里,它们在细胞 j 心中的最靠后排名
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
for (int k = 1; k <= n; k++) {
cin >> a[i][k];
rk[i][a[i][k]] = k;
}
}
cin >> p;
// 预处理 best_pos[j][x]。
// 枚举“被消灭的病毒 x”,以及在细胞 x 心中比它更强的病毒 y,
// 再看这些 y 在目标细胞 j 心中的最大排名是多少。
for (int x = 1; x <= n; x++) {
for (int pos = 1; pos < rk[x][x]; pos++) {
int y = a[x][pos];
for (int j = 1; j <= n; j++) {
best_pos[j][x] = max(best_pos[j][x], rk[j][y]);
}
}
}
vector<int> ans;
if (p == 1) {
// 稳定病毒:自己在自己家里就是最强。
for (int i = 1; i <= n; i++) {
if (a[i][1] == i) {
ans.push_back(i);
}
}
} else {
// 可行病毒:枚举病毒 i,再枚举它最终停留的细胞 j。
for (int i = 1; i <= n; i++) {
bool alive = false;
for (int j = 1; j <= n && !alive; j++) {
// 病毒 i 至少要有机会感染到细胞 j。
if (rk[j][i] > rk[j][j]) {
continue;
}
int pos_i = rk[j][i];
bool ok = true;
// 在细胞 j 心中比 i 更强的每个病毒 x,都必须能够被某个“安全病毒”杀掉。
for (int pos = 1; pos < pos_i; pos++) {
int x = a[j][pos];
if (best_pos[j][x] < pos_i) {
ok = false;
break;
}
}
if (ok) {
alive = true;
}
}
if (alive) {
ans.push_back(i);
}
}
}
cout << ans.size() << '\n';
for (int idx = 0; idx < (int)ans.size(); idx++) {
if (idx) {
cout << ' ';
}
cout << ans[idx];
}
cout << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题最容易误判的地方是把感染过程本身想得太复杂。
稳定病毒只和“自己在自己家里是不是第一名”有关;
可行病毒则只需要固定一个终点细胞,再判断所有更强病毒能不能被更安全的病毒逐个清掉。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
