Preface
一直感觉这东西很高大上,事实上也确实如此。
反正是不知道打错时应该怎么调的了。
上下界
其实就是每条边的流量限制,普通的网络流有上界限制。
但如果有下界限制,问题就显得比较精彩了。
既然有了下界限制,那么对于这个网络流而言,是否有可行流都需要讨论。
无源汇上下界可行流
我们先对可行流进行讨论。
首先,解释一下“无源汇”的意思:把普通有源汇的图转化成每一个点都是源或汇,都可以有无限流量,但必须要满足流量平衡。
而这个问题的定义就是:
能否对每条边进行流量定量,使得每个点满足流量平衡的前提下,每条边也在流量的上下界范围内。
请注意,我们现在关心的仅仅是这个图是否存在一种可行流。
这个问题也是上下界网络流一类问题的核心。所以我将重点分析一下解决这个问题的思路。
解决此问题的核心思想,是调整。
先确定一个看似合法的流,即每条边都已经有一个流量下界了。很多情况下也称这个看似合法的流为初始流,本文也将这样称呼。
但注意到这个流虽然满足了下界,但却违背了流量平衡的限制。
于是,我们想办法通过对一些进行流量调整,然后使得最终流量平衡。
我们定义一个点的 A i A_i Ai为它在初始流中的流入量 - 流出量的值。
分类讨论,如果 A i < 0 A_i\lt 0 Ai<0那么表示 初始流中的流入量小于流出量,我们调整边的流量时需要 多流入这个点一些流量,然而这些流量我们必须要找到一个出路,否则又不满足流量平衡了。于是自然而然的想到设立所谓的超级汇。同理,当 A i > 0 A_i\gt 0 Ai>0时,我们有理由设立所谓的超级源,因为我们需要让流量有一个来路。
于是,根据上面的分析,我们可以自然而然的想到判定上下界是否有可行流的方法:
如果源点的所有出边都流满,或者说汇点所有的入边也流满,那么则证明有可行流。
#include <bits/stdc++.h>
#define F(i,a,b) for(int i=a;i<=b;i++)
#define mem(a,b) memset(a, b, sizeof a)
const int N = 2e2 + 10, M = 5e5 + 10;
using namespace std;
int n, m, s, t, lower, upper, ss, tt, tot, W[M]; bool bz[M];
int d[N], tov[M], nex[M], las[N], len[M], L[M], dis[N];
void link(int x, int y, int l) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = l;
tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}
bool OK() {
queue <int> Q; Q.push(ss); mem(dis,7), dis[ss] = 0;
while (Q.size()) {
int k = Q.front(); Q.pop();
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] > dis[k] + 1)
dis[tov[x]] = dis[k] + 1, Q.push(tov[x]);
}
return dis[tt] < 117901063;
}
int Dinic(int k, int flow) {
if (k == tt)
return flow;
int have = 0;
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] == dis[k] + 1 && !bz[x]) {
int now = Dinic(tov[x], min(flow - have, len[x]));
have += now, len[x] -= now, len[x ^ 1] += now;
if (flow == have)
return flow;
}
if (!have) dis[k] = - 1;
return have;
}
int main() {
scanf("%d%d", &n, &m), tot = 1;
F(i, 1, m) {
scanf("%d%d%d%d", &s, &t, &lower, &upper);
d[s] += lower, d[t] -= lower, link(s, t, upper - lower);
W[i] = tot; L[i] = lower;
}
ss = 0, tt = n + 1; int All = 0;
F(i, 1, n)
if (d[i] > 0) link(i, tt, d[i]); else link(ss, i, - d[i]), All += - d[i];
int sum = 0;
while (OK())
mem(bz, 0), sum = sum + Dinic(ss, 1e9);
if (sum < All)
puts("NO");
else {
puts("YES");
F(i, 1, m)
printf("%d\n", L[i] + len[W[i]]);
}
}
有源汇上下界可行流
在这个问题中,源和汇的流量无限,只要求源的流出量等于汇的流入量,即可以看做源和汇不要求流量平衡。
这让问题显得很难堪。
解决这个问题的思路是把有源汇变为无源汇。
其实很简单,只需让汇点连一条容量为无穷大的边到源点即可。
这样子,我们不需要讨论源点汇点是否满足流量平衡,而只需把整幅图看作一个无源汇的上下界网络流。
因为这个新图里,每个点都可以看做一个源,每个点可以看做一个汇,于是直接按照上面无源汇的做法去做即可。
有源汇上下界最大流/最小流
思路是一样的。
但一个很妙的思路是在残量网络上跑最大流。
因为残量网络已经保证下界限制,在这里继续跑最大流,当然是合法的。
所以最终最大流 = 可行流的流量 + 残量网络上最大流流量
如何算可行流的流量??实际上就是 t → s t\rightarrow s t→s的反向边流量。这是很好理解的。之所以不是 s s ss ss的流量的原因是显然的。只要你理解了为什么要连 t → s t\rightarrow s t→s这条边即可。
在有源汇的图中,我们把每个点都看做了一个源,每个点都看做了一个汇。也就是说,可能不通过原图中的源 s s s,而直接从 s s ss ss出发,到达一个非源点,之后到达 t t tt tt。事实上,可以发现,如果除去 t → s t\rightarrow s t→s这条边,原图中是不可能有第二条边连向 s s s的。
而因为这是一个无源汇的图,所以 s s s点的流出量就等于 t t t点的流入量, t t t点的流入量只能通过 t → s t\rightarrow s t→s这一条边走到 s s s以此保证流量平衡。所以 s → t s\rightarrow t s→t的总流量就是 t → s t\rightarrow s t→s的反向边流量。
而对于最小流的思路也是一样的,反过来跑,从 t t t到 s s s做最大流,然后减去即可。
最大流:
#include <bits/stdc++.h>
#define F(i,a,b) for (int i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)
const int inf = 1e9;
const int N = 300;
const int M = 4e4 + 10;
using namespace std;
int n, m, s, t, ss, tt, x, y, all, sum, lower, upper;
int dis[N], d[N], TOV[M], NEX[M], LAS[N], TOT;
int tov[M], nex[M], len[M], las[N], tot;
void link(int x, int y, int l) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = l;
tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}
bool OK(int ss, int tt) {
queue <int> Q; Q.push(ss); mem(dis, 7); dis[ss] = 0;
while (Q.size()) {
int k = Q.front(); Q.pop();
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] > dis[k] + 1) {
dis[tov[x]] = dis[k] + 1;
Q.push(tov[x]);
}
}
return dis[tt] < 117901063;
}
int dinic(int k, int stop, int flow) {
if (k == stop)
return flow;
int have = 0;
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] == dis[k] + 1) {
int now = dinic(tov[x], stop, min(flow - have, len[x]));
have += now, len[x] -= now, len[x ^ 1] += now;
if (flow == have)
return have;
}
if (!have) dis[k] = - 1;
return have;
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t), tot = 1;
F(i, 1, m) {
scanf("%d%d%d%d", &x, &y, &lower, &upper);
d[x] -= lower, d[y] += lower;
link(x, y, upper - lower);
}
mec(TOV, tov), mec(NEX, nex), mec(LAS, las);
ss = 0, tt = n + 1;
F(i, 1, n)
if (d[i] > 0)
all += d[i], link(ss, i, d[i]);
else
link(i, tt, - d[i]);
link(t, s, inf);
while (OK(ss, tt))
sum = sum + dinic(ss, tt, inf);
if (all == sum) {
sum = len[tot];
mec(tov, TOV), mec(nex, NEX), mec(las, LAS);
while (OK(s, t))
sum = sum + dinic(s, t, inf);
printf("%d\n", sum);
}
else
puts("please go home to sleep");
}
最小流
#include <bits/stdc++.h>
#define I register int
#define F(i,a,b) for (I i = a; i <= b; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)
const int inf = 1e9;
const int N = 5e4 + 20;
const int M = 4e5 + 10;
using namespace std;
int n, m, s, t, stop, ss, tt, x, y, all, sum, lower, upper;
int dis[N], d[N], TOV[M], NEX[M], LAS[N], TOT;
int tov[M], nex[M], len[M], las[N], cur[N], tot;
void link(I x, I y, I l) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = l;
tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}
bool OK(I ss, I tt) {
queue <int> Q; Q.push(ss); mem(dis, - 1); dis[ss] = 0;
while (Q.size()) {
I k = Q.front(); Q.pop();
for (I x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] == - 1) {
dis[tov[x]] = dis[k] + 1;
Q.push(tov[x]);
}
}
return dis[tt] > - 1;
}
int dinic(I k, I flow) {
if (k == stop || flow == 0)
return flow;
I have = 0;
for (I &x = cur[k]; x ; x = nex[x]) {
I y = tov[x];
if (dis[y] == dis[k] + 1 && len[x]) {
I now = dinic(y, min(flow - have, len[x]));
have += now, len[x] -= now, len[x ^ 1] += now;
if (flow == have)
return have;
}
}
if (!have)
dis[k] = - 1;
return have;
}
void Re(I &x) {
char c = getchar(); x = 0;
for (; !isdigit(c); c = getchar());
for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = getchar());
}
int main() {
Re(n),Re(m),Re(s),Re(t), tot = 1;
F(i, 1, m) {
Re(x), Re(y), Re(lower), Re(upper);
d[x] -= lower, d[y] += lower;
link(x, y, upper - lower);
}
mec(TOV, tov), mec(NEX, nex), mec(LAS, las);
ss = 0, tt = n + 1;
F(i, 1, n)
if (d[i] > 0)
all += d[i], link(ss, i, d[i]);
else
if (d[i] < 0)
link(i, tt, - d[i]);
link(t, s, inf);
stop = tt;
while (OK(ss, tt)) {
mec(cur, las);
sum = sum + dinic(ss, inf);
}
if (all == sum) {
sum = len[tot];
mec(tov, TOV), mec(nex, NEX), mec(las, LAS); stop = s;
while (OK(t, s)) {
mec(cur, las);
sum = sum - dinic(t, inf);
}
printf("%d\n", sum);
}
else
puts("please go home to sleep");
}
费用流
在许多问题中,我们不仅想让流量最大,还想让每条边流量乘上其对应花费的总费用最小。
这样就有了费用流的问题。
我们有一种很暴力的思路,即每次找一条费用最小的边去增广,直到不能增广为止。因为是基于SPFA的,所以甚至可以存在负权费用。
这样的代码也很好打:
#include <bits/stdc++.h>
#define F(i,a,b) for (int i = a; i <= b; i ++)
#define mem(a, b) memset(a, b, sizeof a)
const int N = 400 + 10;
const int M = 3e4 + 10;
using namespace std;
int n, m, x, y, c, w, tot, s, t, Ans, Sum; bool vis[N];
int tov[M], nex[M], len[M], cost[M], las[N], dis[N], pre[N];
void ins(int x, int y, int c, int w) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = - w;
}
bool spfa() {
queue <int> Q; Q.push(s); mem(vis, 0), vis[s] = 1; mem(dis, 7), dis[s] = 0;
while (Q.size()) {
int k = Q.front(); Q.pop();
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] > dis[k] + cost[x]) {
dis[tov[x]] = dis[k] + cost[x];
pre[tov[x]] = x;
if (!vis[tov[x]])
Q.push(tov[x]), vis[tov[x]] = 1;
}
vis[k] = 0;
}
return dis[t] < 117901063;
}
int main() {
scanf("%d%d", &n, &m), tot = 1, s = 1, t = n;
F(i, 1, m) {
scanf("%d%d%d%d", &x,&y,&c,&w);
ins(x, y, c, w);
}
while (spfa()) {
int Flow = 1e9;
for (int i = t; pre[i]; i = tov[pre[i] ^ 1])
Flow = min(Flow, len[pre[i]]);
for (int i = t; pre[i]; i = tov[pre[i] ^ 1]) {
Sum = Sum + Flow * cost[pre[i]];
len[pre[i]] -= Flow, len[pre[i] ^ 1] += Flow;
}
Ans += Flow;
}
printf("%d %d\n", Ans, Sum);
}
注意到上面这种单路增广的方式实在是有点暴力。ZKW对此进行了优化,采取SPFA + 多路增广的方式。
注意多路增广时可能有负权圈的存在,所以要加上访问标记,即如果一个点已经访问过则不再访问。这样在保证每次能找到一条非零费用路径增广时,加快速度。
在稠密图上优势明显。
#include <bits/stdc++.h>
#define F(i,a,b) for (int i = a; i <= b; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
const int N = 400 + 10;
const int M = 3e4 + 10;
using namespace std;
int n, m, x, y, c, w, tot, s, t, Ans, Sum; bool vis[N], bz[N];
int tov[M], nex[M], len[M], cost[M], las[N], dis[N];
void ins(int x, int y, int c, int w) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = - w;
}
bool spfa() {
queue <int> Q; Q.push(s); mem(vis, 0), vis[s] = 1; mem(dis, 7), dis[s] = 0;
while (Q.size()) {
int k = Q.front(); Q.pop();
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] > dis[k] + cost[x]) {
dis[tov[x]] = dis[k] + cost[x];
if (!vis[tov[x]])
Q.push(tov[x]), vis[tov[x]] = 1;
}
vis[k] = 0;
}
return dis[t] < 117901063;
}
int dfs(int k, int flow) {
vis[k] = 1;
if (k == t) return flow;
int have = 0;
for (int x = las[k]; x ; x = nex[x])
if (len[x] && !vis[tov[x]] && dis[k] + cost[x] == dis[tov[x]]) {
int now = dfs(tov[x], min(flow - have, len[x]));
if (now)
Sum += now * cost[x], len[x] -= now, len[x ^ 1] += now, have += now;
if (flow == have)
return flow;
}
return have;
}
int main() {
scanf("%d%d", &n, &m), tot = 1, s = 1, t = n;
F(i, 1, m) {
scanf("%d%d%d%d", &x,&y,&c,&w);
ins(x, y, c, w);
}
while (spfa())
for (vis[t] = 1; vis[t]; ) {
mem(vis, 0);
Ans += dfs(s, 1e9);
}
printf("%d %d\n", Ans, Sum);
}
ZKW在此基础上继续优化,加入spfa的SLF优化,继续优化时间。
#include <bits/stdc++.h>
#define F(i,a,b) for (int i = a; i <= b; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)
const int N = 400 + 10;
const int M = 3e4 + 10;
using namespace std;
int n, m, x, y, c, w, tot, s, t, Ans, Sum; bool vis[N], bz[N];
int tov[M], nex[M], len[M], cost[M], las[N], dis[N];
void ins(int x, int y, int c, int w) {
tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = - w;
}
bool spfa() {
deque <int> Q; Q.push_back(s); mem(vis, 0), vis[s] = 1; mem(dis, 7), dis[s] = 0;
while (Q.size()) {
int k = Q.front(); Q.pop_front();
for (int x = las[k]; x ; x = nex[x])
if (len[x] && dis[tov[x]] > dis[k] + cost[x]) {
dis[tov[x]] = dis[k] + cost[x];
if (!vis[tov[x]]) {
vis[tov[x]] = 1;
if (Q.size() && dis[tov[x]] < dis[Q.front()]) Q.push_front(tov[x]); else Q.push_back(tov[x]);
}
}
vis[k] = 0;
}
return dis[t] < 117901063;
}
int dfs(int k, int flow) {
vis[k] = 1;
if (k == t) return flow;
int have = 0;
for (int x = las[k]; x ; x = nex[x])
if (len[x] && !vis[tov[x]] && dis[k] + cost[x] == dis[tov[x]]) {
int now = dfs(tov[x], min(flow - have, len[x]));
if (now)
Sum += now * cost[x], len[x] -= now, len[x ^ 1] += now, have += now;
if (flow == have)
return flow;
}
return have;
}
int main() {
scanf("%d%d", &n, &m), tot = 1, s = 1, t = n;
F(i, 1, m) {
scanf("%d%d%d%d", &x,&y,&c,&w);
ins(x, y, c, w);
}
while (spfa())
for (vis[t] = 1; vis[t]; ) {
mem(vis, 0);
Ans += dfs(s, 1e9);
}
printf("%d %d\n", Ans, Sum);
}
以上就是费用流的几种实现方式。
上下界 + 费用流
终于来了。。。
如果在上下界的基础上加上费用流呢??
比如说:http://120.77.82.93/senior/#main/show/3302
由于费用不相同,所以不能按照上面的方法连边,但事实上有一种美妙的拆边方式:
即先建立源点汇点,超级源和超级汇。
然后尝试连边,每次把一条边 ( u , v , d w , u p , c o s t ) (u,v,dw,up,cost) (u,v,dw,up,cost)拆成三条: ( s s , v , d w , c o s t ) (ss,v,dw,cost) (ss,v,dw,cost) ( u , t t , d w , 0 ) (u,tt,dw,0) (u,tt,dw,0) ( u , v , u p − d w , c o s t ) (u,v,up-dw,cost) (u,v,up−dw,cost)
意思很好理解了。但如何保证下界呢?事实上方法与上面的一样,只要 s s ss ss的出边都流满即可。这样可以保证下界。这样就做完了。
但是对于这道题有一个二次方的,所以要动态加边,而且要拆系数,维护。所以只能打EK,不能用ZKW。
#include <bits/stdc++.h>
#define F(i,a,b) for (int i=a;i<=b;i++)
#define mem(a,b) memset(a,b,sizeof a)
const int N=2e2+10;
const int M=1e5+10;
const int inf=1e9;
using namespace std;
int n,m,lef,in,out, sum,u,v,a,b,L,U; bool vis[N];
int s,t,ss,tt,tot, shu[M],tov[M],nex[M],len[M],cost[M],pre[M],lv[M],rv[M],las[N],dis[N];
struct yls { int u,v,L,U,a,b; } jl[M];
void ins(int x,int y,int l,int w){
if (l==0) return;
tov[++tot]=y,nex[tot]=las[x],las[x]=tot,len[tot]=l,cost[tot]= w;
tov[++tot]=x,nex[tot]=las[y],las[y]=tot,len[tot]=0,cost[tot]=-w;
}
void link(int x,int y,int dw,int up,int w){
ins(x,y,up-dw,w);
ins(ss,y,dw,w);
ins(x,tt,dw,0);
}
bool spfa(int s,int t) {
deque<int> Q; Q.push_front(s); mem(vis, 0), vis[s] = 1, mem(dis, 7), dis[s] = 0;
while (Q.size()) {
int k = Q.front(); Q.pop_front();
for (int x=las[k];x;x=nex[x])
if (len[x]&&dis[tov[x]]>dis[k]+cost[x]) {
dis[tov[x]]=dis[k]+cost[x];
pre[tov[x]] = x;
if (vis[tov[x]]) continue;
vis[tov[x]] = 1;
if (Q.size() && dis[tov[x]] < dis[Q.front()]) Q.push_front(tov[x]); else Q.push_back(tov[x]);
}
vis[k]=0;
}
return dis[t]<117901063;
}
int main(){
scanf("%d%d", &n,&m), s=0,t=n+1, ss=n+2,tt=n+3, tot=1;
F(i,1,n){
scanf("%d%d%d", &lef,&in,&out);
if (lef>0) link(s,i,lef,lef,0);
if (lef<0) link(i,t,-lef,-lef,0);
link(s,i,0,inf,in);link(i,t,0,inf,out);
}
F(i,1,m){
scanf("%d%d%d%d%d%d",&u,&v,&a,&b,&L,&U);
ins(ss,v,L,L*a+b);
ins(u,tt,L,0);
if (L==U) continue;
ins(u,v,1,(2*L+1)*a+b);
lv[i]=rv[i]=1;
jl[i]={u,v,L,U,a,b};
shu[tot-1]=shu[tot]=i;
}
ins(t,s,inf,0); int Ans = 0;
while (spfa(ss,tt)) {
int Flow=inf;
for (int i=tt; pre[i]; i=tov[pre[i]^1])
Flow=min(Flow,len[pre[i]]);
for (int i=tt,j; pre[i]; i=tov[j^1]){
j=pre[i];
int y=shu[j];
sum=sum+cost[j]*Flow;
len[j] -= Flow, len[j ^ 1] += Flow;
if (!y) continue;
if (cost[j]>0) {
lv[y]++;
if (lv[y]>rv[y]) {
rv[y]=lv[y];
u=jl[y].u,v=jl[y].v,L=jl[y].L,U=jl[y].U,a=jl[y].a,b=jl[y].b;
if (rv[y]>U-L) continue;
ins(u,v,1,(2*L+2*lv[y]-1)*a+b);
shu[tot-1]=shu[tot]=y;
}
} else lv[y]--;
}
Ans += Flow;
}
printf("%d\n",sum);
}