UVa 12264 Risk
题目分析
在一个棋盘游戏 Risk\texttt{Risk}Risk 中,玩家控制若干区域,每个区域有若干军队。玩家可以在自己控制的相邻区域之间移动军队,移动过程中每个区域必须始终至少保留一个军队。目标是经过一个回合的移动后,使得所有与对手区域相邻的我方区域(称为边界区域)中,军队数量的最小值尽可能大。
换句话说,我们需要最大化一个值 targettargettarget,使得存在一种移动方案,让每个边界区域的军队数都至少达到 targettargettarget,同时满足移动过程中的约束。
解题思路
这是一个典型的最大值最小化问题,通常可以用二分答案 + 可行性检查的方法解决。
二分答案框架
- 下界 lowlowlow:初始时所有边界区域军队数量的最小值。
- 上界 highhighhigh:我方所有军队的总数(因为军队不可能超过总数)。
- 在 [low,high][low, high][low,high] 范围内二分查找,对于每个 midmidmid,判断是否能让所有边界区域的军队数都 ≥mid\ge mid≥mid。
可行性检查的核心难点
移动军队时有两个关键约束:
- 每个区域在任何时刻至少保留一个军队。
- 军队只能在自己控制的相邻区域间移动。
直接模拟移动过程非常复杂,我们需要一个数学模型来描述军队的重新分配。
网络流建模
本题的标准解法是使用最大流来检查可行性。建模的关键在于如何正确表示军队的流动和最终分布。
拆点技巧
每个我方区域 iii 拆成两个节点:
- 入点 iii:表示区域本身
- 出点 i+ni + ni+n:表示从该区域流出的军队
为什么需要拆点?
拆点可以精确控制每个区域的军队数量:区域 iii 最终的军队数 = 从源点流入 iii 出点的军队 + 区域原有的军队 - 从 iii 出点流出的军队。通过入点和出点之间的边容量,我们可以限制每个区域最终保留的军队数。
边的含义
-
出点 →\rightarrow→ 入点:容量为 a[i]a[i]a[i](区域原有的军队数)。
这条边表示区域 iii 原有的军队必须经过它才能流动到其他地方,且不能超过原有数量。 -
源点 sss →\rightarrow→ 出点:容量根据区域类型不同:
- 如果是边界区域:容量为 a[i]a[i]a[i](所有军队都可以调出)
- 如果是内部区域:容量为 a[i]−1a[i] - 1a[i]−1(必须保留一个军队)
源点代表“可以重新分配的军队池”,这些军队可以流向任何需要的地方。
-
入点 iii →\rightarrow→ 出点 j+nj+nj+n:如果区域 iii 和 jjj 相邻且都是我方的,则添加容量为 ∞\infty∞ 的边。
这表示军队可以从区域 iii 移动到区域 jjj(通过 iii 的入点流入 jjj 的出点)。 -
出点 →\rightarrow→ 汇点 ttt:仅对边界区域添加,容量为 midmidmid。
这条边表示该边界区域最终需要保留 midmidmid 个军队。如果最大流能将这些边全部填满,就说明所有边界区域都能达到 targettargettarget。
可行性判断
设边界区域的数量为 mmm,则所有边界区域需要的军队总数为 m×midm \times midm×mid。
计算从源点到汇点的最大流 flowflowflow,如果 flow≥m×midflow \ge m \times midflow≥m×mid,说明 midmidmid 可行。
为什么这个建模是正确的?
- 军队守恒:每个区域原有的军队通过“出点 →\rightarrow→ 入点”的边进入系统,最终通过“出点 →\rightarrow→ 汇点”的边离开系统(对边界区域)或留在系统中(对内部区域)。
- 移动约束:军队只能通过“入点 →\rightarrow→ 出点”的边在相邻区域间移动。
- 最低保留约束:内部区域的源点连接容量为 a[i]−1a[i]-1a[i]−1,确保了至少保留一个军队;边界区域的汇点连接容量为 midmidmid,确保了最终数量。
代码实现
// Risk
// UVa ID: 12264
// Verdict: Accepted
// Submission Date: 2026-02-23
// UVa Run Time: 0.010s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int INF = 10000;
const int MAXN = 205;
class Dinic {
private:
struct Edge {
int to, cap, flow, rev;
};
vector<Edge> adj[MAXN];
int level[MAXN], cur[MAXN];
public:
void clear() {
for (int i = 0; i < MAXN; i++) adj[i].clear();
}
void addEdge(int u, int v, int cap) {
adj[u].push_back({v, cap, 0, (int)adj[v].size()});
adj[v].push_back({u, 0, 0, (int)adj[u].size() - 1});
}
bool bfs(int s, int t) {
memset(level, -1, sizeof(level));
queue<int> q;
level[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto &e : adj[u]) {
if (level[e.to] < 0 && e.cap > e.flow) {
level[e.to] = level[u] + 1;
q.push(e.to);
}
}
}
return level[t] >= 0;
}
int dfs(int u, int t, int f) {
if (u == t) return f;
for (int &i = cur[u]; i < (int)adj[u].size(); i++) {
Edge &e = adj[u][i];
if (level[e.to] == level[u] + 1 && e.cap > e.flow) {
int d = dfs(e.to, t, min(f, e.cap - e.flow));
if (d > 0) {
e.flow += d;
adj[e.to][e.rev].flow -= d;
return d;
}
}
}
return 0;
}
int maxFlow(int s, int t) {
int flow = 0;
while (bfs(s, t)) {
memset(cur, 0, sizeof(cur));
int f;
while ((f = dfs(s, t, INF)) > 0) flow += f;
}
return flow;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> army(n + 1);
int totalArmy = 0;
for (int i = 1; i <= n; i++) {
cin >> army[i];
totalArmy += army[i];
}
vector<bool> isBorder(n + 1, false);
vector<vector<bool>> adj(n + 1, vector<bool>(n + 1, false));
for (int i = 1; i <= n; i++) {
string line;
cin >> line;
for (int j = 1; j <= n; j++) {
if (line[j - 1] == 'Y') {
adj[i][j] = true;
if (army[i] > 0 && army[j] == 0) isBorder[i] = true;
}
}
}
int borderCount = 0;
for (int i = 1; i <= n; i++)
if (isBorder[i]) borderCount++;
int left = 1, right = totalArmy, ans = 0;
while (left <= right) {
int mid = (left + right) / 2;
Dinic dinic;
int s = 0, t = 2 * n + 1;
for (int i = 1; i <= n; i++) {
if (army[i] == 0) continue;
dinic.addEdge(i + n, i, army[i]);
if (isBorder[i]) {
dinic.addEdge(s, i + n, army[i]);
dinic.addEdge(i + n, t, mid);
} else {
dinic.addEdge(s, i + n, army[i] - 1);
}
}
for (int i = 1; i <= n; i++) {
if (army[i] == 0) continue;
for (int j = 1; j <= n; j++) {
if (i != j && adj[i][j] && army[j] > 0)
dinic.addEdge(i, j + n, INF);
}
}
int flow = dinic.maxFlow(s, t);
if (flow >= borderCount * mid) {
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
cout << ans << endl;
}
return 0;
}
复杂度分析
- 每次 maxFlow\texttt{maxFlow}maxFlow 调用使用 Dinic\texttt{Dinic}Dinic 算法,复杂度 O(EV)O(E \sqrt{V})O(EV),其中 V=2n+2V = 2n + 2V=2n+2,E=O(n2)E = O(n^2)E=O(n2)。
- 二分查找次数为 O(log∑ai)O(\log \sum a_i)O(log∑ai),总复杂度 O(n2nlog∑ai)O(n^2 \sqrt{n} \log \sum a_i)O(n2nlog∑ai),在 n≤100n \le 100n≤100 时完全可行。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)