网络流

无内鬼,来点网络流

网络流是啥?

给你一个有向图 \(G = \{V, E\}\),它的每条边 \((u,v) \in E\) 都有一个非负流量 \(c(u,v)\),而每条不属于其的边的流量为 \(0\)。同时,该图上有两个特殊点:源点 \(s\) 与汇点 \(t\)
一个网络流 \(f : E \rightarrow R\) 是满足如下特性的函数:

  1. 容量限制:\(\forall (u,v) \in E,\ f(u,v) \le c(u,v)\)
    这点体现了一条边的流不能超过其流量。
  2. 斜对称:\(f(u,v) = -f(v,u)\)
    这点在换流的时候有用。
  3. 流守恒:\(\forall u \in V, u \neq s, u\neq t,\ \large\sum_{v \in V} f(u,v) = 0\)
    任意非源汇点的点净流为0。

定义一个流的流量为 \(\text{Max Flow} = \sum_{v\in V} f(s, v) = \sum_{v\in V} f(v, t)\)

最大流

字面意思,求源点到汇点的最大流量。

Folk-Fulkerson算法

我们有一个简单贪心思路:找到一条 \(s\rightarrow t\) 且流量非空的路径,把它上面的流量拉满,计入答案。这个路径就是所谓增广路,增广了原有的流。然而,当我们将一条不在最大流中的边增广了,那么就会出现问题,陷入局部最优解。为此,我们需要进行可反悔贪心。

我们考虑斜对称的作用。将原图的边变成双向边,并定义反向边上增加1流量就等于原边上减少1流量。这样,我们就能通过流量抵消获得单向边的实际流量。这样,非最优决策就会被退出,以此重新决策。

引入一个概念:残量网络。残量网络是一个原图 \(G\) 的子图 \(G' = \{ V', E'\}\),其中每条边的边权都为 \(c'(u,v) = c(u,v) - f(u,v)\)。这样,加载流量的操作就可以转化成残量网络中边权降低,流量抵消的操作就是边权增加。

我们建立流量网络时,记录每条边的出入节点、当前流量、反向边,随后不断跑dfs,每次走剩余容量非0的边来寻找增广路,维护路径上最小流量。dfs返回时执行可反悔贪心,减少当前边剩余容量, 增加反向边剩余容量,最终累加答案。dfs返回一个非负数表示所找到增广路的可用流量,若为0则无增广路,返回/若大于0则缩小剩余流量,返回答案。单次执行的复杂度为 \(O(V + E)\)

然而这种算法的复杂度是 \(O(值域)\) 的。 不加掩饰,直接干上指数
考虑如何优化求解。

Edmond-Karp算法

我们考虑每次增广最短的增广路。这样的复杂度是 \(O(VE^2)\) 的。

稍微证明一下:增广是选出一条路上流最小的边,把它断掉。每次选择最短路保证了每次断掉目前最短的路,因此最短路单增。断掉的边不一定不再更新,但是更新它时最短路一定增加,而最短路最长也就 \(V\),因此最坏复杂度为 \(O(VE)\)。这最短路是01最短路,使用bfs维护,单次复杂度 \(O(E)\),因此总复杂度 \(O(VE^2)\)

大概这么写?
int bfs(int st, int ed) {
	rep(i,1,n) pre[i] = -1, flow[i] = inf;
	queue<int> q;
	pre[st] = 0, q.push(st);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (int i = 1; i <= n; i++) {
			if (i == st or pre[i] != -1 or g[u][i] == 0) continue;
			pre[i] = u;
			flow[i] = min(flow[u], g[u][i]);
			q.push(i);
		}
	} if (flow[ed] == inf) return -1;
	return flow[ed];
}

int EK() {
	int ans = 0;
	while (1) {
		int k = bfs(S, T);
		if (k == -1) break;
		ans += k;
		int ptr = T;
		while (ptr != S) {
			c[pre[ptr]][ptr] -= k, c[ptr][pre[ptr]] += k;
			ptr = pre[ptr];
		} 
	} return ans;
}

说句闲话,EK算法的时间复杂度重写一下是 \(O(V^5)\) 的。因为它每次遍历残量网络后只会找到一条增广路,很退化,考虑如何加速求解。

Dinic算法

Dinic算法引入了“分层图”的概念。具体地,我们按照每个点到源点的最短路长度分组,每组设为一层,最终只保留层间的边。这样的好处在于每条分层图上的边都定在一条残量网络的最短路上。因此,我们可以放心大胆地在分层图上跑dfs,进行多路增广。这样跑出来的结果肯定正确。而且,通过多路增广,我们可以保证一条边只会被增广一次,这又叫做当前弧优化。

其时间复杂度是 \(O(V^2E)\) 的。证明一下:每轮增广最少删一条边,因此总增广次数是 \(O(V)\) 的。每次dfs,由于当前弧优化,复杂度是 \(O(VE)\) 的。总复杂度是 \(O(V^2E)\) 的。

这个复杂度松得离谱。一般而言,如果你有一个 \(V = 1000, E = 10000\) 的图,你可以放心大胆地扔上去Dinic。而且法律规定不能卡Dinic

Dinic
typedef long long ll;

#define v e[i].to
#define d e[i].dis
#define d_pair e[i ^ 1].dis
int head[2][N], mlc = 1;
struct ep {
	int to, next;
	ll dis;
} e[N<<1];
inline void adde(int f, int t, ll fl) {
	e[++mlc].to = t;
	e[mlc].next = head[0][f];
	e[mlc].dis = fl;
	head[0][f] = mlc;
}

queue<int> q;
int dep[N];
bool bfs(int s, int t) {
	rep(i,1,n) dep[i] = 0, head[1][i] = head[0][i];
	q.push(s), dep[s] = 1;
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (register int i = head[0][u]; i; i = e[i].next) {
			if (d and !dep[v]) {
				dep[v] = dep[u] + 1;
				q.push(v);
			}
		}
	} return dep[t];
}

ll dfs(int u, ll in, int t) {
	if (u == t) return in;
	ll out = 0;
	for (register int & i = head[1][u]; i; i = e[i].next) {
		if (d and dep[v] == dep[u] + 1) {
			ll res = dfs(v, min(d, in), t);
			if (res <= 0) dep[v] = 0;
			d -= res;
			d_pair += res;
			in -= res;
			out += res;
		    if (in == 0) break;
		}
	}  return out;
}

ll Dinic(int s, int t) {
	ll ans = 0;
	while (bfs(s, t)) ans += dfs(s, 1e18, t);
	return ans;
}

adde(t1, t2, t3);
adde(t2, t1, 0);

关于 \(O(V^{1.5}E)\) 的 Dinic,可以看这里

HLPP

我不会。

费用流

考虑现在不仅需要求出一张图的最大流。
每条边现在有一个单位费用 \(w_{(i,j)}\),当 \((i,j)\) 这条边流过了 \(f_{(i,j)}\) 的流量时,这条边的费用是 \(w_{(i,j)}\times f_{(i,j)}\)。当获得最大流时需要求出最小费用,这就是最小费用最大流。同时类似地有上下界最大流等一系列对流量存在恶心限制的流题。
这里以最小费用流为例列举一些常见的费用流算法。

EK费用流

现在 EK 不退化了
我们需要首先跑出最大流,在它的基础上跑出最小费用,因此该怎么增广还是怎么增广。
考虑一条增广路 \(p\) 的答案是 \(\sum_{(u,v) \in p}f_p\times w(i,j) = f_p\times \sum_{(u,v) \in p}w(i,j)\),即流量乘以将费用看做距离后的增广路长度,因此我们可以考虑贪心,每次找单位费用之和最小的增广路来增广。这样保证了任意时刻增广出的流都是最小费用流。

考虑现在反向边的费用。
由于反向边增流等于是正向边减流,因此费用也应当是正向边的相反数。
我们发现这里负边权无法避免,所以考虑一个SPFA。

复杂度……把bfs寻找最短路换成了跑spfa得到最短路,所以这部分的时间上界由 \(O(E)\) 上升到了 \(O(VE)\)。总时间复杂度自然变为 \(O(V^2E^2)\)
当然这个上界就更松了

听说 \(\text{M}\color{red}{\text{in_25}}\)把EK卡到上界

Dinic费用流

EK 又退化了

当然和 EK 类似,你把 bfs 换成 spfa 就行了。

Dinic费用流
const int N = 2e5 + 10;
int n, m, S, T, t1, t2, t3, t4, tot_cost;

#define v e[i].to
#define d e[i].flow
#define w e[i].wt
#define d_pair e[i ^ 1].flow
int head[2][N], mlc = 1;
struct ep {
	int to, next, flow, wt;
} e[N<<1];
inline void adde(int f, int t, int fl, int wt) {
	e[++mlc].to = t;
	e[mlc].next = head[0][f];
	e[mlc].flow = fl;
	e[mlc].wt	= wt;
 	head[0][f]  = mlc;
}

queue<int> q;
int dis[N]; bool vis[N];
bool spfa(int s, int t) {
	rep(i,1,n) dis[i] = INT_MAX, vis[i] = false, head[1][i] = head[0][i];
	q.push(s); vis[s] = 1, dis[s] = 0;
	while (!q.empty()) {
		int u = q.front(); q.pop(); vis[u] = 0;
		for (register int i = head[0][u]; i; i = e[i].next) {
			if (d and dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				if (!vis[v]) vis[v] = 1, q.push(v);
			}
		}
	} return dis[t] != INT_MAX;
}

int dfs(int u, int in, int t) {
	if (u == t) return in;
	vis[u] = 1;
	int out = 0;
	for (register int & i = head[1][u]; i; i = e[i].next) {
		if (!vis[v] and d and dis[v] == dis[u] + w) {
			int res = dfs(v, min(d, in), t);
			if (res > 0) tot_cost += res * w, d -= res, d_pair += res, out += res, in -= res;
		    if (in == 0) break;
		}
	} vis[u] = 0;
	return out;
}

int Dinic(int s, int t) {
	int ret = 0, tmp;
	while (spfa(s, t)) while (tmp = dfs(s, INT_MAX, t)) ret += tmp;
	return ret;
}

Primal-Dual

和 Johnson 最短路的思路类似,都是加个势能把边权转正然后跑dij。
首先跑一遍spfa,每个点 \(i\) 的势能是它到原点的最短路长度 \(h_i\)。然后 \((u,v)\) 的边权就转为 \(w_(u,v) + h_u - h_v\)。这样建出来的边权一定非负,并且在新图上的最短路就是原图上的最短路。证明详见 Johnson 最短路。

但每次增广后图的形态会发生变化,因此需要动态更新势能大小。
设增广后原点到 \(i\) 点的距离为 \(d_i\),则 \(h_i\leftarrow h_i+d_i\) 即可。

然后一遍遍跑 dij 就行。记得稠密图别用堆优化。

建图求解

22.12.8
22.12.12
22.12.13

板板

Dinic 最大流
const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, n, m;

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow;
} e[N << 1];
void adde(int u, int v, int f, bool __same = false) {
    e[++ mlc] = { v, head[0][u], f };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], __same ? f : 0 };
    head[0][v] = mlc; 
}

int dep[N]; 
bool bfs(int s, int t) {
    rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
    queue<int> que;
    que.push(s); dep[s] = 1;
    while (que.size()) {
        int u = que.front(); que.pop();
        for (int i = head[0][u], v; i; i = e[i].next) {
            v = e[i].to;
            if (e[i].flow and !dep[v]) {
                dep[v] = dep[u] + 1;
                if (v == t) return 1;
                que.push(v);
            }
        }
    } return dep[t];
}

int dfs(int u, int in, int t) {
    if (u == t or in <= 0) return in;
    int out = 0;
    for (int &i = head[1][u], v; i; i = e[i].next) {
        v = e[i].to;
        if (e[i].flow and dep[v] == dep[u] + 1) {
            int res = dfs(v, min(in, e[i].flow), t);
            in -= res, out += res;
            e[i].flow -= res, e[i ^ 1].flow += res;
            if (in <= 0) break;
        }
    }
	if (out <= 0) dep[u] = 0;
	return out;
}

int Dinic(int s, int t) {
    int ret = 0, fl;
    while (bfs(s, t)) while (fl = dfs(s, inf, t)) ret += fl;
    return ret;
}
Dinic 最小费用最大流
int M, s, t, n, m, t1, t2, t3, t4;

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow, cost;
} e[N << 2];
void adde(int u, int v, int fl, int cs, bool __same = false) {
    e[++ mlc] = { v, head[0][u], fl, cs };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], (__same ? fl : 0), -cs };
    head[0][v] = mlc;
}

int dis[N]; bool vis[N];
bool spfa(int s, int t) {
    rep(i,1,M) dis[i] = inf, vis[i] = false, head[1][i] = head[0][i];
    queue<int> que;
    dis[s] = 0, vis[s] = 1, que.emplace(s);
    while (que.size()) {
        int u = que.front(); que.pop(); vis[u] = 0;
        for (int i = head[0][u], v, w; i; i = e[i].next) {
            v = e[i].to, w = e[i].cost;
            if (e[i].flow and dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                if (!vis[v]) que.emplace(v), vis[v] = 1;
            }
        }
    } return dis[t] != inf;
}

int min_cost;
int dfs(int u, int in, int t) {
    if (u == t or in <= 0) return in;
    vis[u] = 1;
    int out = 0;
    for (int i = head[1][u], v, w, res; i and in > 0; i = e[i].next) {
        head[1][u] = i, v = e[i].to, w = e[i].cost;
        if (!vis[v] and e[i].flow and dis[v] == dis[u] + w) {
            res = dfs(v, min(in, e[i].flow), t);
            if (res > 0) {
                e[i].flow -= res, e[i ^ 1].flow += res;
                in -= res, out += res;
                min_cost += res * w;
                if (in <= 0) break;
            }
        }
    } vis[u] = 0;
    return out;
}

int Dinic(int s, int t) {
    int ret = 0, fl;
    while (spfa(s, t)) while (fl = dfs(s, inf, t)) ret += fl;
    return ret;
}
Dinic 有源汇上下界最大流

P5192 东方文花帖

const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, S, T, n, m, ans, t1, t2, t3, t4;

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow;
} e[N << 1];
void __Adde(int u, int v, int f) {
    e[++ mlc] = { v, head[0][u], f };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], 0 };
    head[0][v] = mlc; 
}

int d[N], sumdeg;
void adde(int u, int v, int Min, int Max) {
    __Adde(u, v, Max - Min);
    d[u] -= Min, d[v] += Min;
}

int dep[N]; 
bool bfs(int s, int t) {
    rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
    queue<int> que;
    que.push(s); dep[s] = 1;
    while (que.size()) {
        int u = que.front(); que.pop();
        for (int i = head[0][u], v; i; i = e[i].next) {
            v = e[i].to;
            if (e[i].flow and !dep[v]) {
                dep[v] = dep[u] + 1;
                if (v == t) return 1;
                que.push(v);
            }
        }
    } return dep[t];
}

int dfs(int u, int in, int t) {
    if (u == t) return in;
    int out = 0;
    for (int &i = head[1][u], v; i; i = e[i].next) {
        v = e[i].to;
        if (e[i].flow and dep[v] == dep[u] + 1) {
            int res = dfs(v, min(in, e[i].flow), t);
            if (res == 0) dep[v] = 0;
            in -= res, out += res;
            e[i].flow -= res, e[i ^ 1].flow += res;
            if (in == 0) break;
        }
    } return out;
}

int Dinic(int s, int t) {
    sumdeg = 0;
    rep(i,1,M) {
        if (i == S or i == T) continue;
        if (d[i] > 0) __Adde(S, i, d[i]), sumdeg += d[i];
        else if (d[i] < 0) __Adde(i, T, -d[i]);
    } adde(t, s, 0, inf);
    int ret = 0;
    while(bfs(S, T)) ret += dfs(S, inf, T);
    if (ret != sumdeg) return -1;
    ret = 0;
    while(bfs(s, t)) ret += dfs(s, inf, t);
    return ret;
}
Dinic 有源汇上下界最小流

P4843 清理雪道

const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, S, T, n, m, ans, t1, t2, t3, t4;

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow;
} e[N << 1];
void __Adde(int u, int v, int f) {
    e[++ mlc] = { v, head[0][u], f };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], 0 };
    head[0][v] = mlc; 
}

int d[N];
void adde(int u, int v, int Min, int Max) {
    __Adde(u, v, Max - Min);
    d[u] -= Min, d[v] += Min;
}

int dep[N]; 
bool bfs(int s, int t) {
    rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
    queue<int> que;
    que.push(s); dep[s] = 1;
    while (que.size()) {
        int u = que.front(); que.pop();
        for (int i = head[0][u], v; i; i = e[i].next) {
            v = e[i].to;
            if (e[i].flow and !dep[v]) {
                dep[v] = dep[u] + 1;
                if (v == t) return 1;
                que.push(v);
            }
        }
    } return dep[t];
}

int dfs(int u, int in, int t) {
    if (u == t) return in;
    int out = 0;
    for (int &i = head[1][u], v; i; i = e[i].next) {
        v = e[i].to;
        if (e[i].flow and dep[v] == dep[u] + 1) {
            int res = dfs(v, min(in, e[i].flow), t);
            if (res == 0) dep[v] = 0;
            in -= res, out += res;
            e[i].flow -= res, e[i ^ 1].flow += res;
            if (in == 0) break;
        }
    } return out;
}

int Dinic(int _s, int _t) {
    rep(i,1,M) {
        if (i == S or i == T) continue;
        if (d[i] > 0) __Adde(S, i, d[i]);
        else if (d[i] < 0) __Adde(i, T, -d[i]);
    } 
    while(bfs(_s, _t)) dfs(_s, inf, _t);
    adde(t, s, 0, inf);
    while(bfs(_s, _t)) dfs(_s, inf, _t);
    return e[mlc].flow;
}
Dinic 有源汇上下界可行流

P4194 矩阵

int M, s, t, S, T, n, m;

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow;
} e[N << 1];
void __Adde(int u, int v, int f) {
    e[++ mlc] = { v, head[0][u], f };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], 0 };
    head[0][v] = mlc; 
}

int d[N];
void adde(int u, int v, int Min, int Max) {
    __Adde(u, v, Max - Min);
    d[u] -= Min, d[v] += Min;
}

int dep[N]; 
bool bfs(int s, int t) {
    rep(i,1,M) dep[i] = 0, head[1][i] = head[0][i];
    queue<int> que;
    que.push(s); dep[s] = 1;
    while (que.size()) {
        int u = que.front(); que.pop();
        for (int i = head[0][u], v; i; i = e[i].next) {
            v = e[i].to;
            if (e[i].flow and !dep[v]) {
                dep[v] = dep[u] + 1;
                if (v == t) return 1;
                que.push(v);
            }
        }
    } return dep[t];
}

int dfs(int u, int in, int t) {
    if (u == t) return in;
    int out = 0;
    for (int &i = head[1][u], v; i; i = e[i].next) {
        v = e[i].to;
        if (e[i].flow and dep[v] == dep[u] + 1) {
            int res = dfs(v, min(in, e[i].flow), t);
            if (res == 0) dep[v] = 0;
            in -= res, out += res;
            e[i].flow -= res, e[i ^ 1].flow += res;
            if (in == 0) break;
        }
    } return out;
}

int sumdeg;
int Dinic(int _s = S, int _t = T) {
    int ret = 0;
    rep(i,1,M) {
        if (i == S or i == T) continue;
        if (d[i] > 0) __Adde(S, i, d[i]), sumdeg += d[i];
        else if (d[i] < 0) __Adde(i, T, -d[i]);
    } 
    __Adde(t, s, inf);
    while(bfs(_s, _t)) ret += dfs(_s, inf, _t);
    return ret;
}
Dinic 有源汇最小费用可行流

支线剧情

const int N = 2e6 + 5;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int M, s, t, S, T, n, m, ans, t1, t2, t3, t4;

int head[2][N], mlc = 1;
struct ep {
    int to, next, flow, cost;
} e[N << 2];
void __Adde(int u, int v, int fl, int cs, bool __same = false) {
    e[++ mlc] = { v, head[0][u], fl, cs };
    head[0][u] = mlc;
    e[++ mlc] = { u, head[0][v], (__same ? fl : 0), -cs };
    head[0][v] = mlc;
}

int d[N], min_cost;
void adde(int u, int v, int Min, int Max, int cost) {
    __Adde(u, v, Max - Min, cost);
    d[u] -= Min, d[v] += Min, min_cost += cost * Min;
}

int dis[N]; bool vis[N];
bool spfa(int s, int t) {
    rep(i,1,M) dis[i] = inf, vis[i] = false, head[1][i] = head[0][i];
    queue<int> que;
    dis[s] = 0, vis[s] = 1, que.emplace(s);
    while (que.size()) {
        int u = que.front(); que.pop(); vis[u] = 0;
        for (int i = head[0][u], v, w; i; i = e[i].next) {
            v = e[i].to, w = e[i].cost;
            if (e[i].flow and dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                if (!vis[v]) que.emplace(v), vis[v] = 1;
            }
        }
    } return dis[t] != inf;
}

int dfs(int u, int in, int t) {
    if (u == t or in <= 0) return in;
    vis[u] = 1;
    int out = 0;
    for (int i = head[1][u], v, w, res; i and in > 0; i = e[i].next) {
        head[1][u] = i, v = e[i].to, w = e[i].cost;
        if (!vis[v] and e[i].flow and dis[v] == dis[u] + w) {
            res = dfs(v, min(in, e[i].flow), t);
            if (res > 0) {
                e[i].flow -= res, e[i ^ 1].flow += res;
                in -= res, out += res;
                min_cost += res * w;
                if (in <= 0) break;
            }
        }
    } vis[u] = 0;
    return out;
}

int Dinic(int _s, int _t) {
    rep(i,1,M) {
        if (i == S or i == T) continue;
        if (d[i] > 0) __Adde(S, i, d[i], 0);
        else if (d[i] < 0) __Adde(i, T, -d[i], 0);
    }
    // while(bfs(_s, _t)) dfs(_s, inf, _t);
    adde(t, s, 0, inf, 0);
    while(spfa(_s, _t)) dfs(_s, inf, _t);
    return min_cost;
}
posted @ 2022-08-02 10:45  joke3579  阅读(103)  评论(1编辑  收藏  举报