一类带负圈的最小费用流

First Post:

Last Update:

之前复习网络流的时候好像没有复习扎实,把这玩意漏掉了。

事实证明,“就看看而不写代码”确实挺容易忘的。

注意:下文所讨论的流都允许一个与 不相连的环出现。

下文的四元组 表示一条从 ,流量为 ,费用为 的边。

一般不带负圈的最小费用流,大家使用自己喜欢的方式就跑过去了。

但是带负圈的最小费用流,会给最短路算法带来极大的困难,导致增广出现问题。

我们就需要一个办法,把图里的负权边都去掉。

注意到我们在建网络流图的时候,会对称地建一条反着的边,费用为原边权的相反数,表示退流。

那么我们在这里,能不能先把负权边都流满,然后借助反向的正权边来完成”退流“工作呢?

显然是可以的。

对于一条负权边 ,我们先把该边流满,将答案预先加上 ,然后建出反向边 ,用以退流。

但这种做法还有一个问题,即所有负权边满流的网络,不一定满足流量平衡的限制。

设原图源汇为 ,我们新建虚拟源点、虚拟汇点 ,用 跑遍网络流,来满足流量平衡的限制。

具体地,假设我们处理了一条负权边 ,那么 点就应该多流入 的流量, 点应该多流出 的流量,我们通过连边 来表达这两个限制,因为最后基于 跑出来的网络流是流量平衡的,我们把这里连的虚拟边删掉,就正好满足 点多流入 点多流出

所以,对于一条负权边,我们会将其拆为三条边 。然后我们先以 为源点, 为汇点,跑一遍网络流。在残量网络上,忽略与 相邻的边,再以 为源汇,跑一遍网络流,将两遍网络流得到的答案相加即可。

需要注意的是,就算原图中没有源汇,这个方法也是可行的,此时这个问题被叫做最小费用循环流(或者说最小费用可行流)。

此时,在原问题中,我们一次会增广一个环,假设这个环除了某条负权边 外,剩下的边费用为

如果 ,那么这个负环需要增广,在新图上,有 那么我们会倾向与走剩下的那条路径,也就是不退流。反之,因为是正环,所以我们会倾向于不增广,在新图上就是走这条 ,把流退掉。

这样可以从另一个方面理解其正确性。

需要注意的是,在求解循环流的时候,需要注意原题面有没有限制流量上界。有的话,在通过 增广时就要加以限制。

事实上,如果读者对上下界网络流较为熟悉,应该能看出来,这就是对上下界网络流的一种应用。

附上模板题代码:

洛谷 P7173

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <bits/stdc++.h>
using namespace std;
const int N = 2e2 + 5;
typedef long long ll;
const ll llinf = 0x3f3f3f3f3f3f3f3f;
int n,m,S,T;
int num[N];
namespace Flow{
const int N = 2e3 + 5,M = 2e6 + 5;
int fir[N],nxt[M],to[M],w[M],cost[M],ect = 1;
inline void addedge(int u1,int v1,int w1,int c1)
{ nxt[++ect] = fir[u1];fir[u1] = ect;to[ect] = v1;w[ect] = w1;cost[ect] = c1;}
inline void ins(int u1,int v1,int w1,int c1) { addedge(u1,v1,w1,c1);addedge(v1,u1,0,-c1);}
ll dis[N],h[N];
bool vst[N];
int tot;
inline void Clr(int _n)
{
tot = _n;ect = 1;
for(int i = 1;i <= tot;i++) fir[i] = 0;
}
void spfa(int S,int T)
{
for(int i = 1;i <= tot;i++) dis[i] = llinf,vst[i] = 0;
queue<int> Q;
Q.push(S);dis[S] = 0;vst[S] = 1;
while(!Q.empty())
{
int x = Q.front();Q.pop();vst[x] = 0;
for(int i = fir[x],y;y = to[i],i;i = nxt[i])
if(w[i] && dis[y] > dis[x] + cost[i])
{
dis[y] = dis[x] + cost[i];
if(!vst[y]) Q.push(y),vst[y] = true;
}
}
for(int i = 1;i <= tot;i++) h[i] = dis[i];
}
int pn[N],pe[N],flow[N];
struct node{
int id;ll dis;
node(){}
node(const int _id,const ll _dis):id(_id),dis(_dis){}
bool operator < (const node &rhs) const { return dis > rhs.dis;}
};
bool Dijkstra(int S,int T,int F = 1e9)
{
for(int i = 1;i <= tot;i++)
pe[i] = pn[i] = vst[i] = 0,dis[i] = llinf;
priority_queue<node> Q;
flow[S] = F;Q.emplace(S,dis[S] = 0);
while(!Q.empty())
{
int x = Q.top().id;Q.pop();
if(vst[x]) continue;
vst[x] = true;
for(int i = fir[x],y,ww;y = to[i],ww = cost[i] + h[x] - h[y],i;i = nxt[i])
if(w[i] && dis[y] > dis[x] + ww)
{
dis[y] = dis[x] + ww;
pn[y] = x;pe[y] = i;flow[y] = min(flow[x],w[i]);
Q.emplace(y,dis[y]);
}
}
return dis[T] < llinf;
}
pair<ll,ll> MCMF(int S,int T)
{
ll F = 0,res = 0;
spfa(S,T);
while(Dijkstra(S,T))
{
for(int i = 1;i <= tot;i++) if(vst[i]) h[i] += dis[i];
int now = T;
res += flow[T] * h[T];
while(now != S)
w[pe[now]] -= flow[T],w[pe[now] ^ 1] += flow[T],now = pn[now];
F += flow[T];
}
return make_pair(F,res);
}
}
ll ans = 0;
int main()
{
cin >> n >> m >> S >> T;
Flow::Clr(n + 2);
for(int i = 1;i <= m;i++)
{
int x,y,f,c;
cin >> x >> y >> f >> c;
if(c >= 0)
Flow::ins(x,y,f,c);
else
{
num[x] -= f;num[y] += f;
Flow::ins(y,x,f,-c);
ans += 1ll * c * f;
}
}
int SS = n + 1,TT = n + 2;
for(int i = 1;i <= n;i++)
{
if(num[i] > 0) Flow::ins(SS,i,num[i],0);
if(num[i] < 0) Flow::ins(i,TT,-num[i],0);
}
ans += Flow::MCMF(SS,TT).second;
pair<ll,ll> res = Flow::MCMF(S,T);
cout << res.first << ' ' << res.second + ans << endl;
return 0;
}