UVa 12092 Paint the Roads
题目分析
本题要求我们在一个有向图中选择若干条边进行粉刷,使得:
- 被粉刷的边可以划分成若干个不相交的环
- 每个顶点恰好出现在 kkk 个环中
- 最小化粉刷边的总成本
关键条件转化
题目中两个核心约束需要仔细分析:
约束一:每个顶点出现在 kkk 个环中
- 在一个环中,每个顶点恰好有一条出边和一条入边
- 因此,如果一个顶点出现在 k 个环中,意味着它有 kkk 条出边 和 kkk 条入边 被选中
- 于是,每个顶点的出度 = 入度 = $$k
约束二:被粉刷的边可以划分成若干个不相交的环
- 对于一个有向图,如果每个顶点的出度 = 入度,那么该图的边集一定可以分解成若干个有向环(欧拉定理)
- 因此,这个约束等价于:被粉刷的边构成的子图中,每个顶点的出度 = 入度
综合两个约束:我们需要选择一个边的子集,使得每个顶点的出度 = 入度 = kkk,并最小化被选边的总费用。
模型抽象
这是一个典型的流量平衡问题:
- 每个顶点需要流出 kkk 个单位(出度要求)
- 每个顶点需要流入 kkk 个单位(入度要求)
- 每条边 (uuu → vvv) 可以选择或不选,如果选择则 uuu 流出 111,vvv 流入 111,并产生费用
这正是最小费用流可以解决的模型。
解题思路
拆点建图
将每个顶点 iii 拆成两个节点:
- 左点 LiL_iLi:代表顶点的出边端
- 右点 RiR_iRi:代表顶点的入边端
建图方式:
-
建立超级源点 SSS,向每个左点 LiL_iLi 连边,容量为 kkk,费用为 000
→ 表示顶点 iii 需要有 kkk 个出度 -
建立超级汇点 TTT,每个右点 RiR_iRi 向 TTT 连边,容量为 kkk,费用为 000
→ 表示顶点 iii 需要有 kkk 个入度 -
对于原图中的每条有向边 (u→v)(u \rightarrow v)(u→v),长度为 ddd:
从左点 LuL_uLu 到右点 RvR_vRv 连边,容量为 111,费用为 ddd
→ 表示选择这条边时,uuu 贡献 111 个出度,vvv 贡献 111 个入度,花费 ddd
流量含义
- 从 SSS 到 LuL_uLu 的流量:顶点 uuu 被选中的出边数量
- 从 LuL_uLu 到 RvR_vRv 的流量:边 (u,v)(u,v)(u,v) 被选中
- 从 RvR_vRv 到 TTT 的流量:顶点 vvv 被选中的入边数量
总流量 = ∑i出度i=k×n\sum_{i} \text{出度}_i = k \times n∑i出度i=k×n
总费用 = 所有被选边的长度之和
可行性判断
- 跑 最小费用最大流,得到最大流量 flowflowflow 和最小费用 costcostcost
- 如果 flow=k×nflow = k \times nflow=k×n,说明每个顶点的出度入度都达到了 kkk,可行,答案为 costcostcost
- 否则,无法满足条件,输出 −1-1−1
复杂度分析
- 节点数:2n+2≤822n + 2 \le 822n+2≤82
- 边数:m+2n≤2080m + 2n \le 2080m+2n≤2080
- 流量值:k×n≤100k \times n \le 100k×n≤100
- 使用 SPFA\texttt{SPFA}SPFA 实现的费用流,复杂度 O(F⋅E⋅V)O(F \cdot E \cdot V)O(F⋅E⋅V),完全可行
正确性证明
必要性:如果存在满足条件的边集,则每个顶点出度 = 入度 = kkk。在流网络中,令 S→LiS \to L_iS→Li 通过 kkk 单位流量,对于每条被选的边 (u,v)(u,v)(u,v),在 Lu→RvL_u \to R_vLu→Rv 上通过 111 单位流量,Rv→TR_v \to TRv→T 也能收到对应流量。这样得到一个流量为 k×nk \times nk×n 的可行流,费用等于边权和。因此最小费用流不会大于最优解。
充分性:如果最小费用流得到 flow=k×nflow = k \times nflow=k×n,则每个 S→LiS \to L_iS→Li 和 Ri→TR_i \to TRi→T 都流满 kkk,即每个顶点的出度 = 入度 = kkk。由欧拉定理,这样的有向图可以分解成若干个环,满足题意。且费用流保证了总费用最小。
注意事项
- 重边处理:题目保证两个城市之间同方向最多一条路,因此每条边建一次容量为 111 即可
- 零费用边:边长度可能为 000,不影响算法
- 多测试用例:每次重新初始化费用流结构体
- 变量命名冲突:代码中汇点变量名不要和测试用例数 TTT 冲突,故将测试用例数命名为 TT\texttt{TT}TT
代码实现
// Paint the Roads
// UVa ID: 12092
// Verdict: Accepted
// Submission Date: 2026-02-12
// UVa Run Time: 0.000s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
struct MCMF {
struct Edge {
int to, cap, cost, rev;
};
int n;
vector<vector<Edge>> g;
vector<int> dist, preNode, preEdge;
vector<bool> inq;
MCMF(int n) : n(n) {
g.resize(n);
dist.resize(n);
preNode.resize(n);
preEdge.resize(n);
inq.resize(n);
}
void addEdge(int from, int to, int cap, int cost) {
g[from].push_back({to, cap, cost, (int)g[to].size()});
g[to].push_back({from, 0, -cost, (int)g[from].size() - 1});
}
bool spfa(int s, int t, int &flow, int &cost) {
fill(dist.begin(), dist.end(), INF);
fill(inq.begin(), inq.end(), false);
dist[s] = 0;
queue<int> q;
q.push(s);
inq[s] = true;
preNode[s] = -1;
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = false;
for (int i = 0; i < (int)g[u].size(); i++) {
Edge &e = g[u][i];
if (e.cap > 0 && dist[e.to] > dist[u] + e.cost) {
dist[e.to] = dist[u] + e.cost;
preNode[e.to] = u;
preEdge[e.to] = i;
if (!inq[e.to]) {
q.push(e.to);
inq[e.to] = true;
}
}
}
}
if (dist[t] == INF) return false;
int f = INF;
for (int v = t; v != s; v = preNode[v]) {
int u = preNode[v];
f = min(f, g[u][preEdge[v]].cap);
}
flow += f;
cost += f * dist[t];
for (int v = t; v != s; v = preNode[v]) {
int u = preNode[v];
Edge &e = g[u][preEdge[v]];
e.cap -= f;
g[e.to][e.rev].cap += f;
}
return true;
}
PII minCostMaxFlow(int s, int t) {
int flow = 0, cost = 0;
while (spfa(s, t, flow, cost));
return {flow, cost};
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int TT;
cin >> TT;
while (TT--) {
int n, m, k;
cin >> n >> m >> k;
// 节点编号:
// 0 ~ n-1: 左点 (出点)
// n ~ 2n-1: 右点 (入点)
// 2n: 源点 S
// 2n+1: 汇点 T
int S = 2 * n, T = 2 * n + 1;
MCMF mcmf(2 * n + 2);
// 源点到左点,容量 k,费用 0
for (int i = 0; i < n; i++)
mcmf.addEdge(S, i, k, 0);
// 右点到汇点,容量 k,费用 0
for (int i = 0; i < n; i++)
mcmf.addEdge(i + n, T, k, 0);
// 读入道路
for (int i = 0; i < m; i++) {
int f, t, d;
cin >> f >> t >> d;
// 左点 f 到右点 t+n,容量 1,费用 d
mcmf.addEdge(f, t + n, 1, d);
}
PII r = mcmf.minCostMaxFlow(S, T);
if (r.first == k * n) cout << r.second << '\n';
else cout << -1 << '\n';
}
return 0;
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)