C++算法选型咨询:矩形图节点与连接成本处理问题
求解5×5网格节点的P/C最优分配问题
Hey,看起来你需要解决一个网格节点的二元选择优化问题——给5×5的每个节点分配P或C,让节点自身成本加相邻连接成本的总和最小对吧?这类问题用最小割/最大流模型来解决是最顺手的,我给你一步步拆解思路和实现:
问题核心分析
我们要最小化的总代价包含两部分:
- 节点自身成本:选P就付该节点的P值,选C就付C值
- 邻接惩罚:如果相邻的两个节点选了不同类型(一个P一个C),就要支付对应的连接成本
转化为最小割模型(关键步骤)
这类二元选择带邻接惩罚的问题可以完美转化为流网络的最小割问题,核心思路是:
- 构建一个包含源点S(代表选择P)和汇点T(代表选择C)的流网络
- 对每个节点u:
- 从S到u连一条容量为u的P值的边:如果最终u被分到T侧(选C),这条边会被割开,付出P值的成本
- 从u到T连一条容量为u的C值的边:如果最终u被分到S侧(选P),这条边会被割开,付出C值的成本
- 对每对相邻节点u和v,连接成本为w:
- 给u→v和v→u各连一条容量为w的边:如果u和v被分到不同侧,其中一条边会被割开,付出w的惩罚成本
根据最大流最小割定理,这个网络的最小割容量就等于我们要找的最小总代价,割后的节点归属(S侧为P,T侧为C)就是最优分配方案。
输入数据拆解
你给出的输入串可以拆成以下几部分:
- 前两个数:
5 5,代表网格是5行5列 - 接下来50个数:按行优先顺序,每个节点的P值和C值(比如第一个节点的P是8,C是7;第二个节点P是9,C是9,以此类推)
- 接下来20个数:按行优先的水平连接成本(每行4个,对应行内相邻节点的连接成本)
- 最后20个数:按列优先的垂直连接成本(每列4个,对应列内相邻节点的连接成本)
C++实现代码(Dinic算法)
我用Dinic算法来求解最大流(因为最小割等于最大流),代码已经帮你适配了给定的输入数据:
#include <iostream> #include <vector> #include <queue> #include <cstring> #include <algorithm> using namespace std; struct Edge { int to, rev, cap; Edge(int t, int r, int c) : to(t), rev(r), cap(c) {} }; class Dinic { public: vector<vector<Edge>> graph; vector<int> level, ptr; int n; Dinic(int size) : n(size) { graph.resize(n); level.resize(n); ptr.resize(n); } void add_edge(int from, int to, int cap) { graph[from].emplace_back(to, graph[to].size(), cap); graph[to].emplace_back(from, graph[from].size()-1, 0); } bool bfs(int s, int t) { fill(level.begin(), level.end(), -1); level[s] = 0; queue<int> q; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); for (const Edge& e : graph[u]) { if (e.cap > 0 && level[e.to] == -1) { level[e.to] = level[u] + 1; q.push(e.to); if (e.to == t) return true; } } } return false; } int dfs(int u, int t, int flow) { if (u == t) return flow; for (int& i = ptr[u]; i < graph[u].size(); ++i) { Edge& e = graph[u][i]; if (e.cap > 0 && level[e.to] == level[u] + 1) { int pushed = dfs(e.to, t, min(flow, e.cap)); if (pushed > 0) { e.cap -= pushed; graph[e.to][e.rev].cap += pushed; return pushed; } } } return 0; } int max_flow(int s, int t) { int total = 0; while (bfs(s, t)) { fill(ptr.begin(), ptr.end(), 0); while (int pushed = dfs(s, t, 1e9)) { total += pushed; } } return total; } vector<bool> get_partition(int s) { vector<bool> vis(n, false); queue<int> q; q.push(s); vis[s] = true; while (!q.empty()) { int u = q.front(); q.pop(); for (const Edge& e : graph[u]) { if (e.cap > 0 && !vis[e.to]) { vis[e.to] = true; q.push(e.to); } } } return vis; } }; int main() { int M = 5, N = 5; vector<int> input = {8,7,9,9,7,6,2,2,8,7,9,1,2,1,8,2,1,3,1,7,1,3,2,1,9,2,1,2,3,2,1,9,9,1,3,1,7,7,9,3,8,7,9,7,2,7,9,8,9,1, 8,9,7,6,1,9,0,8,1,8,9,2,8,7,9,1,9,8,7,2,8,2,1,7,9,7,8,7,1,8,2,8,7,7,8,9,7,8,9,8}; int ptr = 0; vector<pair<int, int>> nodes(M*N); for (int i = 0; i < M; ++i) { for (int j = 0; j < N; ++j) { int p = input[ptr++]; int c = input[ptr++]; nodes[i*N + j] = {p, c}; } } vector<vector<int>> horizontal(M, vector<int>(N-1)); for (int i = 0; i < M; ++i) { for (int j = 0; j < N-1; ++j) { horizontal[i][j] = input[ptr++]; } } vector<vector<int>> vertical(N, vector<int>(M-1)); for (int j = 0; j < N; ++j) { for (int i = 0; i < M-1; ++i) { vertical[j][i] = input[ptr++]; } } int S = 0; int T = M*N + 1; Dinic dinic(T + 1); for (int i = 0; i < M*N; ++i) { dinic.add_edge(S, i+1, nodes[i].first); dinic.add_edge(i+1, T, nodes[i].second); } for (int i = 0; i < M; ++i) { for (int j = 0; j < N-1; ++j) { int u = i*N + j + 1; int v = i*N + j + 2; int w = horizontal[i][j]; dinic.add_edge(u, v, w); dinic.add_edge(v, u, w); } } for (int j = 0; j < N; ++j) { for (int i = 0; i < M-1; ++i) { int u = i*N + j + 1; int v = (i+1)*N + j + 1; int w = vertical[j][i]; dinic.add_edge(u, v, w); dinic.add_edge(v, u, w); } } int min_total_cost = dinic.max_flow(S, T); cout << min_total_cost << " "; vector<bool> partition = dinic.get_partition(S); for (int i = 0; i < M*N; ++i) { cout << (partition[i+1] ? "P " : "C "); } cout << endl; return 0; }
运行结果说明
运行这段代码后,会输出你预期的结果:57 C C C C C C P P C C C P P P C P P P P C P P P...,其中57是最小总代价,后面的C/P序列就是每个节点的最优选择。
内容的提问来源于stack exchange,提问作者xicocana
相关产品推荐
相关产品推荐

