题目分析

本题要求我们在一个有向图中选择若干条边进行粉刷,使得:

  1. 被粉刷的边可以划分成若干个不相交的环
  2. 每个顶点恰好出现在 kkk 个环
  3. 最小化粉刷边的总成本

关键条件转化

题目中两个核心约束需要仔细分析:

约束一:每个顶点出现在 kkk 个环中

  • 在一个环中,每个顶点恰好有一条出边和一条入边
  • 因此,如果一个顶点出现在 k 个环中,意味着它有 kkk 条出边kkk 条入边 被选中
  • 于是,每个顶点的出度 = 入度 = $$k

约束二:被粉刷的边可以划分成若干个不相交的环

  • 对于一个有向图,如果每个顶点的出度 = 入度,那么该图的边集一定可以分解成若干个有向环(欧拉定理)
  • 因此,这个约束等价于:被粉刷的边构成的子图中,每个顶点的出度 = 入度

综合两个约束:我们需要选择一个边的子集,使得每个顶点的出度 = 入度 = kkk,并最小化被选边的总费用。

模型抽象

这是一个典型的流量平衡问题

  • 每个顶点需要流出 kkk 个单位(出度要求)
  • 每个顶点需要流入 kkk 个单位(入度要求)
  • 每条边 (uuuvvv) 可以选择或不选,如果选择则 uuu 流出 111vvv 流入 111,并产生费用

这正是最小费用流可以解决的模型。


解题思路

拆点建图

将每个顶点 iii 拆成两个节点:

  • 左点 LiL_iLi:代表顶点的出边端
  • 右点 RiR_iRi:代表顶点的入边端

建图方式:

  1. 建立超级源点 SSS,向每个左点 LiL_iLi 连边,容量为 kkk,费用为 000
    → 表示顶点 iii 需要有 kkk 个出度

  2. 建立超级汇点 TTT,每个右点 RiR_iRiTTT 连边,容量为 kkk,费用为 000
    → 表示顶点 iii 需要有 kkk 个入度

  3. 对于原图中的每条有向边 (u→v)(u \rightarrow v)(uv),长度为 ddd
    从左点 LuL_uLu 到右点 RvR_vRv 连边,容量为 111,费用为 ddd
    → 表示选择这条边时,uuu 贡献 111 个出度,vvv 贡献 111 个入度,花费 ddd

流量含义

  • SSSLuL_uLu 的流量:顶点 uuu 被选中的出边数量
  • LuL_uLuRvR_vRv 的流量:边 (u,v)(u,v)(u,v) 被选中
  • RvR_vRvTTT 的流量:顶点 vvv 被选中的入边数量

总流量 = ∑i出度i=k×n\sum_{i} \text{出度}_i = k \times ni出度i=k×n
总费用 = 所有被选边的长度之和

可行性判断

  • 最小费用最大流,得到最大流量 flowflowflow 和最小费用 costcostcost
  • 如果 flow=k×nflow = k \times nflow=k×n,说明每个顶点的出度入度都达到了 kkk,可行,答案为 costcostcost
  • 否则,无法满足条件,输出 −1-11

复杂度分析

  • 节点数:2n+2≤822n + 2 \le 822n+282
  • 边数:m+2n≤2080m + 2n \le 2080m+2n2080
  • 流量值:k×n≤100k \times n \le 100k×n100
  • 使用 SPFA\texttt{SPFA}SPFA 实现的费用流,复杂度 O(F⋅E⋅V)O(F \cdot E \cdot V)O(FEV),完全可行

正确性证明

必要性:如果存在满足条件的边集,则每个顶点出度 = 入度 = kkk。在流网络中,令 S→LiS \to L_iSLi 通过 kkk 单位流量,对于每条被选的边 (u,v)(u,v)(u,v),在 Lu→RvL_u \to R_vLuRv 上通过 111 单位流量,Rv→TR_v \to TRvT 也能收到对应流量。这样得到一个流量为 k×nk \times nk×n 的可行流,费用等于边权和。因此最小费用流不会大于最优解。

充分性:如果最小费用流得到 flow=k×nflow = k \times nflow=k×n,则每个 S→LiS \to L_iSLiRi→TR_i \to TRiT 都流满 kkk,即每个顶点的出度 = 入度 = kkk。由欧拉定理,这样的有向图可以分解成若干个环,满足题意。且费用流保证了总费用最小。


注意事项

  1. 重边处理:题目保证两个城市之间同方向最多一条路,因此每条边建一次容量为 111 即可
  2. 零费用边:边长度可能为 000,不影响算法
  3. 多测试用例:每次重新初始化费用流结构体
  4. 变量命名冲突:代码中汇点变量名不要和测试用例数 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;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐