# 网络流:消圈算法
注:下文中的权均表示费用。
# 消圈定理
在某个流 中,如果其残余网络中没有负圈(剩余流量为 的边视为不存在),那它一定是当前流量下的最小费用,否则一定不是。
# 证明
假设一个网络,所有边的容量都是 。

如果流量走上路的话,其残余网络(黑箭头)变为:

因为上路的边的流量占满了,所以现在上路只有反边。
显然 为负圈,沿此负圈增广(每条边的流量+1),环上每个点的入流量仍然等于出流量(原流为可行流)。
流量在圈中增广,总的流量既没有增加,也没有减少,只不过是流量从费用更少的地方流过 (),从费用大的地方退流而已(),流过的流量和退掉的流量是相等的,实质上只是将从 流出的流量的方向改变,使得费用更小。
网络流的反边给了我们一个很好的反悔机制,使得我们可以对任意一个流 ,通过消负圈(可能不止一个),来得到它当前流量下的最小费用流。

可以看到,沿着负圈增广之后,已经没有负圈存在了,已经达到了当前流量下的最小费用流(也就是最小费用最大流)。所以只要有负圈,就可以增广达到更小费用。
# 应用
求最小费用最大流时,可以先跑出一条可行最大流,然后通过不断消圈调整出最小费用。
更广泛用于残余网络寻找更优解。
# POJ2175 Evacuation Plan
# 题面
原题面很长。
给出已达到最大流的残余网络,求出其是否已达到最小费用,如果未达到则找出更优方案。
# 思路
消圈模板,建出网络后利用 SPFA,如果一个节点被更新了 次则说明图中一定存在负环。题目中没有说必须是最优解,因此只要将负圈上的流量调整 即可。
注意一个节点被更新 次不代表其一定在负权圈内。正确做法是从这个节点 开始不断捯它的前驱,如果发现某个节点 被访问了两遍,则说明 一定在负权圈内,再根据 去捯前驱调整负权圈。
# 图解
好像有几个地方标错了 QAQ 凑合看吧





# 代码
#include <stdio.h> | |
#include <string.h> | |
#include <queue> | |
using namespace std;  | |
#define il inline | |
template <typename T> il T abs(T x) { return x > 0 ? x : -x; }  | |
const int N = 110, M = N * N << 1, INF = 0x3f3f3f3f;  | |
struct coor {  | |
int x, y, z;  | |
} a[N], b[N];  | |
struct node {  | |
int u, v, w, f, next;  | |
} e[M];  | |
int h[N << 1], tot = 0;  | |
bool vis[N << 1];  | |
int n, m, s, t;  | |
int cnt[N << 1], pre[N << 1];  | |
il void add(int u, int v, int w, int f) {  | |
e[tot] = (node) {u, v, w, f, h[u]};  | |
h[u] = tot++;  | |
} | |
int bp[N][N], dis[N << 1], p[N][N], occ[N];  | |
deque<int> q;  | |
bool cyc[N << 1];  | |
il void check(int v) {  | |
do {  | |
cyc[v] = true;  | |
v = e[pre[v]].u;  | |
} while(!cyc[v]);  | |
int u = v;  | |
do {  | |
--e[pre[v]].w;  | |
++e[pre[v]^1].w;  | |
v = e[pre[v]].u;  | |
} while(u != v);  | |
for(int i=1; i<=m; ++i)  | |
for(int j = h[n+i]; j != -1; j = e[j].next)  | |
if(e[j].v != t) bp[e[j].v][i] = e[j].w;  | |
printf("SUBOPTIMAL\n");  | |
for(int i=1; i<=n; ++i) {  | |
for(int j=1; j<=m; ++j) printf("%d ", bp[i][j]);  | |
printf("\n");  | |
    } | |
} | |
int main() {  | |
scanf("%d%d", &n, &m);  | |
for(int i=1; i<=n; ++i)  | |
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);  | |
for(int i=1; i<=m; ++i)  | |
scanf("%d%d%d", &b[i].x, &b[i].y, &b[i].z);  | |
for(int i=1; i<=n; ++i)  | |
for(int j=1; j<=m; ++j)  | |
scanf("%d", &p[i][j]),  | |
occ[j] += p[i][j];  | |
memset(h, -1, sizeof h);  | |
s = 0, t = n+m+1;  | |
for(int i=1; i<=n; ++i) {  | |
add(s, i, a[i].z, 0);  | |
add(i, s, 0, 0);  | |
    } | |
for(int i=1; i<=n; ++i) {  | |
for(int j=1; j<=m; ++j) {  | |
int f = abs(a[i].x - b[j].x) + abs(a[i].y - b[j].y) + 1;  | |
add(i, n+j, INF, f);  | |
add(n+j, i, p[i][j], -f);  | |
        } | |
    } | |
for(int i=1; i<=m; ++i) {  | |
add(n+i, t, b[i].z - occ[i], 0);  | |
add(t, n+i, occ[i], 0);  | |
    } | |
memset(dis, 0x3f, sizeof dis);  | |
q.push_front(s);  | |
vis[s] = true;  | |
++cnt[s];  | |
dis[s] = 0;  | |
while(!q.empty()) {  | |
int u = q.front();  | |
q.pop_front();  | |
vis[u] = false;  | |
for(int i = h[u]; i != -1; i = e[i].next) {  | |
int v = e[i].v;  | |
if(e[i].w and dis[v] > dis[u] + e[i].f) {  | |
pre[v] = i;  | |
dis[v] = dis[u] + e[i].f;  | |
if(!vis[v]) {  | |
if(!q.empty() and dis[v] >= dis[q.front()]) q.push_back(v);  | |
else q.push_front(v);  | |
vis[v] = true;  | |
++cnt[v];  | |
if(cnt[v] == t+1) {  | |
check(v);  | |
return 0;  | |
                    } | |
                } | |
            } | |
        } | |
    } | |
printf("OPTIMAL\n");  | |
return 0;  | |
} | 
算法证明中的图片引自 Sengxian’s Blog。

