Loading

网络流基础 复习笔记

尚未全部完成。无零基础内容。

目录

  • 模板代码

  • 一些坑点

  • 定理套路

  • 题目分析

模板代码

1. 网络最大流

P3376 【模板】网络最大流
用于解决求一个网络最大流问题(废话)。下面使用了效率较高且实现简单的 Dinic 算法。

而且貌似我并没有加「当前弧优化」也能 AC ?

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

#define Maxn 10010
#define Maxm 100010

using namespace std;

const int INF = 0x3f3f3f;

int read() {
	int x = 0,f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

int N,M,S,T;

struct Edge {
	int next,to,dis;
}
edge[2 * Maxm];
int head[Maxn],edge_num = 1;

void add_edge(int from,int to,int dis) {
	edge[++edge_num].next = head[from];
	edge[edge_num].to = to;
	edge[edge_num].dis = dis;
	head[from] = edge_num;
}

queue <int> Q;
int dist[Maxn];

bool bfs() {
	memset(dist,0,sizeof(dist));
	while(!Q.empty()) Q.pop();
	Q.push(S); dist[S] = 1;
	while(!Q.empty()) {
		int u = Q.front();
		Q.pop();
		for(int i = head[u];i;i = edge[i].next) {
			int v = edge[i].to; 
			if(!dist[v] && edge[i].dis) {
				dist[v] = dist[u] + 1;
				Q.push(v);
				if(v == T) return 1;
			}
		}
	}
	return 0;
}

int dinic(int u,int flow) {
	if(u == T) return flow;
	int rest = flow;
	for(int i = head[u];i && rest;i = edge[i].next) {
		int v = edge[i].to;
		if(dist[u] + 1 == dist[v] && edge[i].dis) {
			int k = dinic(v,min(rest,edge[i].dis));
			if(!k) dist[v] = 0;
			rest -= k;
			edge[i].dis -= k;
			edge[i ^ 1].dis += k;
		} 
	}
	return flow - rest;
}

int main() {
	N = read(); M = read();
	S = read(); T = read();
	int u,v,w;
	for(int i = 1; i <= M; i++) {
		u = read(); v = read(); w = read();
		add_edge(u,v,w);
		add_edge(v,u,0);
	}
	int maxflow = 0, flow = 0;
	while(bfs()) {
		flow = dinic(S,INF);
        maxflow += flow;
	}
	cout << maxflow;
	return 0;
}

除此之外还有 EK 算法,由于效率较低再次不再赘述。但是 EK 算法也有他的用武之地。费用流的实现一般就会建立在 EK 的基础上。如下:

2. 最小费用最大流

P3381 【模板】最小费用最大流
用于解决在 最大流的基础上,使得费用最小的问题(废话)。

设图的边集为 \(E\),某条边 \(e\) 的流量为 \(f_e\),费用为 \(c_e\),则整个图的费用定义为 \(cost(G)=\sum_{e \in E } f_e \times c_e\)

下面是用的为 EK 算法。在最大流 EK 模板的基础上,将 bfs 部分改为 SPFA(不用 Dij 的原因是因为可能有负权)即可求解最小费用最大流。最大费用最大流同理。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

#define Maxn 5010
#define Maxm 50010

using namespace std;

int N, M, S, T, maxflow, ans;

int read() {
	int x = 0,f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while('0' <= c && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

struct Edge {
	int next, to;
	int dis, cost;
}
edge[Maxm * 2];
int head[Maxn], edge_num = 1;

void add_edge(int from, int to, int dis, int cost) {
	edge[++edge_num].next = head[from];
	edge[edge_num].to = to;
	edge[edge_num].dis = dis;
	edge[edge_num].cost = cost;
	head[from] = edge_num;
}

int dist[Maxn], incf[Maxn], pre[Maxn];
bool inq[Maxn];
void init() {
	memset(dist, 63, sizeof(dist));
	memset(inq, 0, sizeof(inq));
	memset(incf, 63, sizeof(incf));
	memset(pre, 0, sizeof(pre));
}
bool SPFA() {
	bool flag = 0;
	queue <int> Q;
	init();
	Q.push(S); inq[S] = 1; dist[S] = 0;
	while(!Q.empty()) {
		int u = Q.front(); Q.pop(); inq[u] = 0;
		for(int i = head[u]; i; i = edge[i].next) {
			if(!edge[i].dis) continue;
			int v = edge[i].to;
			if(dist[v] > dist[u] + edge[i].cost) {
				dist[v] = dist[u] + edge[i].cost;
				incf[v] = min(incf[u], edge[i].dis);
				if(v == T) flag = 1;
				pre[v] = i;
				if(!inq[v]) {inq[v] = 1; Q.push(v);}
			}
		}
	}
	return flag;
}

void upd() {
	int x = T;
	while(x != S) {
		int i = pre[x];
		edge[i].dis -= incf[T];
		edge[i ^ 1].dis += incf[T];
		x = edge[i ^ 1].to;
	}
	maxflow += incf[T];
	ans += dist[T] * incf[T];
}

int main() {
	N = read(); M = read(); S = read(); T = read();
	int u, v, c, w;
	for(int i = 1; i <= M; i++) {
		u = read(); v = read(); c = read(); w = read();
		add_edge(u, v, c, w);
		add_edge(v, u, 0, -w);
	}
	while(SPFA()) upd();
	cout << maxflow << " " << ans << endl;
	return 0; 
}

此外还有「有上下界网络流问题」。由于笔者太菜(暴露了)这里暂时不提。

一些坑点

此处以 Dinic 算法为例(因为最常用)。

这个人怎么犯这么多傻逼错误啊 ?

  • 由于要处理反向边,初始时要令 edge_num\(1\)

  • 每次 bfs 前,dist 数组归零。

  • 这句话 int k = dinic(v,min(rest,edge[i].dis));

    其中的 rest 一定不要写成 flow!(脑残几次了)

套路定理

比较常用的两个定理,很菜是吧。

  • 最大流最小割定理

    众所周知,最大流 = 最小割,感性可证(?)。

  • 最小路径覆盖 = 点数 - 最大流

    请注意,此处所谓 最大流 的建图并非原图。详见模板题 P2764 最小路径覆盖问题

网络流的关键在于建模。以下是几个套路:

  • 拆点 如果遇到这样的字眼,比如「只能经过某个点 \(x\) 次」之类的,想到拆点。把一个点拆成 \(2\) 个点,中间用一条有流量的边连接。

    例题:P2153 [SDOI2009] 晨跑

  • 分组 其实这个有点涉及到二分图了。遇到「选出尽量多的元素,但某些元素之间不能共存」之类的问题,可以想到 最大点权独立集

    先将所有元素分为互不相交的两组。建立超级源汇点 \(S,T\), 把 \(S\)\(T\) 分别与两组中每个点相连,流量全为 \(1\) ; 再将互不相容的点相连,流全为 \(INF\)。假设一开始我们选取了全部元素,现在必须要删除一些元素。显然删除元素的个数即为该网络的 最小割,亦即最大流。

    例题:hdu1569 方格取数。

  • 灵感建模 反正看到有关匹配「匹配」的字眼,可以考虑网络流。

    例题:P2756 飞行员配对方案问题

题目分析

先咕着

posted @ 2020-08-02 10:29  Sqrtyz  阅读(70)  评论(0)    收藏  举报