把每个武将所在行的次大默契值看成他起手后能保住的最强搭档,再取所有次大值的最大者;同时用高边构成匹配证明小涵一定能赢。
OJ: luogu
题目 ID: P1199
难度:普及+/提高
标签:思维博弈图论构造最大次大值
日期: 2026-06-20 10:19
题意
有 n 个武将,任意两人之间都有一个互不相同的默契值。
小涵和计算机轮流选将,小涵先手。
计算机每次都会按题目指定的贪心策略,优先抢走最能破坏小涵下一步最强组合的那个自由武将。
所有武将被平分后,双方各自在自己军队里选出默契值最高的一对武将出战,默契值更大的一方获胜。
要求判断:
- 小涵是否一定能赢
- 如果能赢,在所有可能胜利结局中,小涵那对出战武将的最大默契值是多少
思路
先看一个可以完整模拟规则的小数据暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20;
int n;
int w[MAXN][MAXN];
int all_mask;
map<long long, int> memo_win;
map<long long, int> memo_best;
// 计算某一方当前军队中的最强组合。
int best_pair_value(int mask) {
int ans = -1000000000;
for (int i = 0; i < n; i++) {
if (((mask >> i) & 1) == 0) {
continue;
}
for (int j = i + 1; j < n; j++) {
if ((mask >> j) & 1) {
ans = max(ans, w[i][j]);
}
}
}
return ans;
}
long long encode_state(int xmask, int cmask, int turn) {
return ((long long) xmask << 21) | ((long long) cmask << 1) | turn;
}
pair<int, int> dfs(int xmask, int cmask, int turn) {
long long key = encode_state(xmask, cmask, turn);
if (memo_win.count(key)) {
return make_pair(memo_win[key], memo_best[key]);
}
int free_mask = all_mask ^ xmask ^ cmask;
if (free_mask == 0) {
int x_best = best_pair_value(xmask);
int c_best = best_pair_value(cmask);
if (x_best > c_best) {
memo_win[key] = 1;
memo_best[key] = x_best;
}
else {
memo_win[key] = 0;
memo_best[key] = -1000000000;
}
return make_pair(memo_win[key], memo_best[key]);
}
if (turn == 0) {
int can_win = 0;
int best_value = -1000000000;
for (int i = 0; i < n; i++) {
if ((free_mask >> i) & 1) {
pair<int, int> ret = dfs(xmask | (1 << i), cmask, 1);
if (ret.first) {
can_win = 1;
best_value = max(best_value, ret.second);
}
}
}
memo_win[key] = can_win;
memo_best[key] = best_value;
return make_pair(can_win, best_value);
}
else {
// 计算机的策略是:
// 从“小涵已选武将”与“当前自由武将”的所有配对中,
// 找到默契值最大的那一对,并抢走其中的自由武将。
int choose = -1;
int best_match = -1;
for (int i = 0; i < n; i++) {
if (((xmask >> i) & 1) == 0) {
continue;
}
for (int j = 0; j < n; j++) {
if ((free_mask >> j) & 1) {
if (w[i][j] > best_match) {
best_match = w[i][j];
choose = j;
}
}
}
}
pair<int, int> ret = dfs(xmask, cmask | (1 << choose), 0);
memo_win[key] = ret.first;
memo_best[key] = ret.second;
return ret;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
cin >> w[i][j];
w[j][i] = w[i][j];
}
}
all_mask = (1 << n) - 1;
pair<int, int> ret = dfs(0, 0, 0);
cout << ret.first << '\n';
if (ret.first) {
cout << ret.second << '\n';
}
return 0;
}暴力会枚举小涵每一步选谁,计算机则按题意固定回应,最后比较双方最强二人组。 它只能用于很小的数据,但很适合帮我们验证结论。
第一步:固定第一手看会发生什么
假设小涵第一手选了武将 i。
由于此时小涵军队里只有 i 一个人,所以计算机下一手一定会:
- 在所有自由武将里找一个和
i默契值最大的 - 然后把它抢走
这就意味着:
i最好的搭档一定被破坏- 小涵围绕
i还能保住的最好搭档,只能是第二好的那个
所以对每个武将 i,只要看它这一行里的:
- 最大值
- 次大值
其中次大值,就是“小涵如果第一手拿 i,最终至少能保住的最好组合值”。
于是先得到一个候选答案:
ans = max(第 i 行次大值)
第二步:为什么这个值一定能赢
关键性质是:
- 所有 严格大于
ans的边,一定构成一个匹配
原因很简单:
如果某个点连出去有两条边都大于 ans,那么这个点所在行的次大值也会大于 ans,这和 ans 的定义矛盾。
既然这些高边构成匹配,那么在选将过程中:
- 计算机一次最多只能拿走某条高边的一个端点
- 小涵总能在自己的下一步把对应的另一个端点拿走
所以计算机最终不可能同时拥有某条高边的两个端点,
也就不可能形成默契值大于 ans 的组合。
而小涵只要从“次大值等于 ans”的那一行对应武将起手,
就能保证自己最后拿到一条默契值至少为 ans 的边。
因此:
- 小涵一定能赢
- 最大可保证值就是
ans
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 505;
int n;
int w[MAXN][MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
cin >> w[i][j];
w[j][i] = w[i][j];
}
}
int answer = 0;
// 对于每个武将 i:
// 如果小涵第一步先拿 i,
// 计算机一定会抢走和 i 默契值最大的那个武将。
// 那么小涵以后能和 i 组成的最好搭档,只能是“这一行的次大值”对应的人。
// 在所有起手武将里取最大的次大值,就是最终答案。
for (int i = 1; i <= n; i++) {
int first_max = 0;
int second_max = 0;
for (int j = 1; j <= n; j++) {
if (i == j) {
continue;
}
if (w[i][j] > first_max) {
second_max = first_max;
first_max = w[i][j];
}
else if (w[i][j] > second_max) {
second_max = w[i][j];
}
}
answer = max(answer, second_max);
}
cout << 1 << '\n';
cout << answer << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题表面是博弈,真正的关键却是一个很短的图论结论:
- 固定第一手后,计算机会破坏这一行最大值
- 所以这一行能保住的是次大值
- 所有超过全局答案的边会形成匹配,计算机拿不成完整的一条
所以最后答案就是:
- 所有行的次大值中的最大值
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
