网络流
网络流入门到入坟
\(1.\) 流网络
\(G=(V,E)\):有向图,s:源点,t:汇点,边权:流量。不存在反向边(存在可以加点)。
可行流 \(f\):容量限制,流量守恒对于每个点流入流出相同(不考虑反向边)。
最大流:最大可行流。
残留网络:与可行流一一对应,由原图边和反向边组成,原图边,容量减流量,原图反向边,退流。
原网络的可行流加残留网络的可行流也是原网络的可行流。流量值相加。
增广路径:在残留网络里,沿着边权大于零的边走,如果能到达重点则称为增广路径。
若可行流没有增广路径,则该可行流为最大流。
割:把点集分成两个子集:\(S\),\(T\),\(s∈S\),\(t∈T\)。
割的容量:由 \(S\) 指向 \(T\) 的边权之和。(不考虑反向边)
割的流量:由 \(S\) 指向 \(T\) 的流量之和减去由 \(T\) 指向 \(S\) 的流量之和。
割的流量一定小于等于割的容量。
最大流小于等于最小割
\(2.\) 最大流最小割定理
最大流 \(f\) 的残留网络中不存在增广路。存在割的流量等于最大流的流量。
\(EK\) 算法:复杂度 \(O(nm^2)\)。迭代,每次先找一条增广路,更新残留网络,求增广路径上容量的最小值,更新所有正向边减去 \(k\),所有反向边增加 \(k\),利用一个 \(pre\) 数组记录一下路径。
const int N = 1e3 + 10;
const int M = 2e4 + 10; //残留网络
const int inf = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx; //f容量
int q[N], d[N], pre[N];//宽搜队列,记录从起点到当前点的容量最小值
bool st[N];
inline void add(int a, int b, int c){//维护残留网络
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;//容量
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;//反向边容量为0
}
inline bool bfs(){//bfs找增广路
int hh = 0, tt = 0;
memset(st, false, sizeof st);
q[0] = S, st[S] = true, d[S] = inf;
while(hh <= tt){
int t = q[hh ++];//取队头元素
for(int i = h[t]; ~ i; i = ne[i]){//遍历出边
int ver = e[i];
if(!st[ver] && f[i]){//没有经历过且容量大于零
st[ver] = true;//遍历过
d[ver] = min(d[t], f[i]);//维护最小容量
pre[ver] = i;//更新pre数组
if(ver == T) return true;//找到了
q[++ tt] = ver;//入队列
}
}
}
return false;
}
inline int EK(){
int r = 0;
while(bfs()){
r += d[T];
for(int i = T; i != S; i = e[pre[i] ^ 1]) //从后向前找反向边
f[pre[i]] -= d[T], f[pre[i] ^ 1] += d[T];//将使用的流量更新残留网络
}
return r;//返回最大流
}
signed main(){
read(n), read(m), read(S), read(T);
memset(h, -1, sizeof h);
while(m --){
int a, b, c;
read(a), read(b), read(c);
add(a, b, c);
}
print(EK());
return 0;
}
\(Dinic\) 算法:复杂度 \(O(n^2m)\)。
const int N = 1e4 + 10;
const int M = 2e5 + 10;
const int inf = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx; //f容量
int q[N], d[N], cur[N];//优化
inline void add(int a, int b, int c){//维护残留网络
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;//容量
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;//反向边容量为0
}
//当前弧优化
//记录流满的边数,从下一条边开始枚举
inline bool bfs(){ //判断有无增广路,并建立分层图
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){ //没有被搜过,且容量大于0
d[ver] = d[t] + 1;//层数,其实就是到起点的最短距离
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver; //没找到加入队列继续宽搜
}
}
}
return false;
}
inline int find(int u, int limit){//从起点开始能流到u的最大流为limit
if (u == T) return limit;
int flow = 0; //把当前满的跳过
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i; // 当前弧优化
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){//搜下一层
int t = find(ver, min(f[i], limit - flow));//流量
if (!t) d[ver] = -1;//删点
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
inline int dinic(){
int r = 0, flow;
while (bfs()) while (flow = find(S, inf)) r += flow;
//只要有增广路,在当前残留网络分层图中寻找所有增广路径累加增广流量
return r;
}
signed main(){
scanf("%d%d%d%d", &n, &m, &S, &T);
memset(h, -1, sizeof h);
while (m -- ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
printf("%d\n", dinic());
return 0;
}
\(3.\) 题目
用网络流解题,需要可行流与可行解一一对应。
\(4.\) 费用流
在一个流网络里,所有最大可行流中费用的最小、最大值。
最小费用最大流:退费用,\(SPFA\)求一条源点到汇点的最短路,更新残留网络,(\(EK\))。
const int N = 5e3 + 10;
const int M = 2e5 + 10;
const int inf = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
//记录推最短路,走到每个点的最大流量
bool st[N];
inline void add(int a, int b, int c, int d){
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++;
}
inline bool spfa(){
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = inf;
while(hh != tt){
int t = q[hh ++];
if(hh == N) hh = 0;
st[t] = false;
for(int i = h[t]; ~ i; i = ne[i]){
int ver = e[i];
if(f[i] && d[ver] > d[t] + w[i]){
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(f[i], incf[t]);
if(!st[ver]){
q[tt ++] = ver;
if(tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
inline void EK(int &flow, int &cost){
flow = cost = 0;
while(spfa()){
int t = incf[T];
flow += t, cost += t * d[T];
for(int i = T; i != S; i = e[pre[i] ^ 1]){
f[pre[i]] -= t, f[pre[i] ^ 1] += t;
}
}
}
signed main(){
read(n), read(m), read(S), read(T);
memset(h, -1, sizeof h);
while(m --){
int a, b, c, d;
read(a), read(b), read(c), read(d);
add(a, b, c, d);
}
int flow, cost;
EK(flow, cost);
print(flow), cout << " ", print(cost);
return 0;
}