1. 流网络
概述:在图论中,一个流网络是指一个有向图,其中每条边都有一个容量限制并可以接受流,满足每一条边的流量不会超过它的容量。一道流必须符合一个结点的进出的流量相同的限制,除非这是一个源点──只有向外的流,或是一个汇点──只有向内的流。这种流网络可以用来建模很多实际问题,如液体在管道中的流动、装配线上部件的流动、电网中电流的流动和通信网络中信息的流动等。
流网络(Flow Network):一个有向图\(G=(V,E)\),图中每条边\((u,b) \in E\)有一个非负的容量值\(c(u,v) \geqslant 0\),称为容量函数。规定两个特殊的结点,源点\(s\)和汇点\(t\)。\((G,c,s,t)\)就称为一个流网络。
- 流(Flow):一个流网络中的流是一个实值函数\(f:V \times V \rightarrow \mathbb{R}\),满足以下三条性质:
- 容量限制(Capacity Constraints):\(f(u,v) \leqslant c(u,v)\),即一条边上的流量不能超过这条边上的容量。
- 斜对称(Skew Symmetry):\(f(u,v)=-f(v,u)\),由\(u\)到\(v\)的净流必须是由\(v\)到\(u\)的净流的相反数。
- 流守恒(Flow Conservation):\(\forall v \in V/\{s,t\},\ \sum_{(u,v) \in E}{f(u,v)} = \sum_{(v,z) \in E}{f(v,z)}\),即对于除源点和汇点外的每个结点,流入的流量等于流出的流量。
2. 最大流 Dinic算法
最大流问题:我们希望在不违反任何容量限制的情况下,使从源点出发的流的总量最大或者流入汇点的流的总量最大。我们想知道这个最大值是多少,这就是最大流问题。
Ford-Fulkerson方法:Ford-Fulkerson方法可以用来解决最大流问题。之所以称其为“方法”而不是“算法”,是因为它包含了几种运行时间各不相同的具体实现,Dinic算法就是其中的一种实现。Ford-Fulkerson方法的基本思想是:开始时网络中初始的流值为\(0\),在每一次迭代中,在“残余网络”中寻找一条“增广路径”,沿增广路径增加流量,直到残余网络中不存在增广路径为止。最大流最小割定理保证了在算法结束时,该算法将获得一个最大流。
残余网络(Residual Network):一条边的剩余容量定义为\(c_f(u,v)=c(u,v)-f(u,v)\),由此可以构造出剩余网络\(G_f(V,E_f)\),它显示了网络中剩余的可用容量。形式化地定义,对于一个流网络\((G,c,s,t)\),它的残余网络为\((G_f,c_f,s,t)\),其中\(G_f=(V,E_f)\),\(E_f=\{(u,v) \in V \times V: c_f(u,v) > 0\}\),\(c_f(u,v)=c(u,v)-f(u,v)\)。
增广路径(augmenting path):增广路径是一条路径\((u_1,u_2,\cdots,u_k)\),其中\(u_1=s\),\(u_k=t\),\(c_f(u_i,u_{i+1})>0,(1 \leqslant 1 < k)\)。增广路径表示沿这条路径传送更多的流量是可能的。当残余网络中没有增广路径时得到最大流。
割(Cut):一个流网络\((G,c,s,t),G=(V,E)\)的割\(C=(S,T)\)是顶点集合\(V\)的一个划分,满足\(s \in S\),\(t \in T\)。割\((S,T)\)的容量是\(c(S,T)=\sum_{u \in S, v \in T}{c(u,v)}\)。
最大流最小割定理:最大流 = 最小割。(限制流量的瓶颈)
Dinic算法:每次对残余网络BFS标注层次图,即用数组\(level[i]\)记录\(i\)点在BFS树中的深度。然后严格按照层次顺序不断DFS寻找增广路,即增广路上结点的层次编号严格递增。如果残余网络中已经不存在到增广路了就重新标BFS注层次图,再次不断DFS增广,直到源点与汇点不再连通。累计每次增广得到的流量就得到最大流。
Code
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 10;
const int M = 2e3 + 10;
struct Edge
{
int to, c, next;
Edge() {}
Edge(int to, int c, int next) : to(to), c(c), next(next) {}
} edge[M];
int adj[N], tot;
void init()
{
memset(adj, -1, sizeof(adj));
tot = 0;
}
void add(int u, int v, int c)
{
edge[tot] = Edge(v, c, adj[u]);
adj[u] = tot++;
edge[tot] = Edge(u, 0, adj[v]);
adj[v] = tot++;
}
int level[N];
queue q;
bool bfs(int s, int t)
{
while (!q.empty()) q.pop();
memset(level, -1, sizeof(level));
level[s] = 0; q.push(s);
while (!q.empty())
{
int u = q.front(); q.pop();
for (int i = adj[u]; i != -1; i = edge[i].next)
{
Edge &e = edge[i];
if (e.c && level[e.to] == -1)
{
level[e.to] = level[u] + 1;
if (e.to == t) return true;
q.push(e.to);
}
}
}
return false;
}
int cur[N];
int dfs(int u, int t, int flow)
{
if (u == t) return flow;
for (int &i = cur[u]; i != -1; i = edge[i].next)
{
Edge &e = edge[i];
if (e.c && level[e.to] > level[u])
{
int f = dfs(e.to, t, min(flow, e.c));
if (f)
{
e.c -= f;
edge[i ^ 1].c += f;
return f;
}
}
}
return 0;
}
int dinic(int s, int t)
{
int flow = 0;
while (bfs(s, t))
{
memcpy(cur, adj, sizeof(adj));
int f;
while (f = dfs(s, t, INF)) flow += f;
}
return flow;
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
init();
while (m--)
{
int u, v, c;
scanf("%d%d%d", &u, &v, &c);
add(u, v, c);
}
int s, t;
scanf("%d%d", &s, &t);
int ans = dinic(s, t);
printf("%d\n", ans);
return 0;
}
Input
第一行给出结点数\(n\)和边数\(m\),接下来的\(m\)行,每行给出两个三个整数\(u\),\(v\)和\(c\),表示从\(u\)到\(v\)有一条容量为\(c\)的边。最后一行给出源点\(s\)和汇点\(t\)。
Output
输出一个整数,表示最大流。
Sample Input
6 9
0 1 10
0 2 10
1 2 2
1 3 4
1 4 8
2 4 9
3 5 10
4 3 6
4 5 10
0 5
Sample Output
19