网络流笔记

网络流笔记

1. 最大流

流网络中流量最大的可行流。

1.1. EK 算法

每次利用 BFS 找到一条增广路。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 5e4, inf = 1e18;

int n, m, s, t, u, v, head[N], cyf[N], tot = 1, pre[N];
bool vis[N], vg[N];

struct edge
{
	int to, nxt, flow;
} e[N << 1];

void addedge(int u, int v, int w)
{
	e[++tot] = {v, head[u], w}, head[u] = tot;
}

bool bfs(int s, int t)
{
	queue<int> q;
	memset(vis, 0, sizeof vis);
	q.push(s), vis[s] = 1, cyf[s] = inf;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if(!vis[v] && e[i].flow)
			{
				cyf[v] = min(cyf[u], e[i].flow);
				q.push(v), vis[v] = 1, pre[v] = i;
				if(v == t) return 1;
			}
		}
	}
	return 0;
}

int EK(int s, int t)
{
	int maxflow = 0, cur;
	while(bfs(s, t))
	{
		cur = t, maxflow += cyf[t];
		vector<int> g;
		while(cur != s)
		{
			int pr = pre[cur];
			e[pr].flow -= cyf[t];
			e[pr ^ 1].flow += cyf[t];
			if(cur != t) g.push_back(cur > n ? cur - n : cur);
			cur = e[pr ^ 1].to;
		}
	}
	return maxflow;
}

1.2. Dinic 算法

对于 EK 算法,我们每次执行完 BFS 寻找增广路径之后,都要重新进行 BFS 寻找新的增广路径,但
是我们在 BFS 之后进行 DFS,就可以实现多路同时增广。

当前弧优化:如果某一时刻我们已经知道边 \((u, v)\) 已经增广到极限,边 \((u, v)\) 已无剩余容量或 \(v\) 的后侧已增广至阻塞),则 \(u\) 的流量没有必要再尝试流向出边 \((u, v)\). 据此,对于每个结点 \(u\),我们维护 \(u\) 的出边表中第一条还有必要尝试的出边。习惯上,我们称维护的这个指针为当前弧。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1e3 + 5;

int n, m, u, v, w, head[N], cur[N], dep[N], s, t, tot = 1;

struct edge
{
	int to, nxt, flow;
} e[N << 2];

void addedge(int u, int v, int w)
{
	e[++tot] = {v, head[u], w}, head[u] = tot;
}

bool bfs(int s, int t)
{
	queue<int> q;
	memset(dep, 0, sizeof dep);
	memcpy(cur, head, sizeof cur);
	q.push(s), dep[s] = 1;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if(!dep[v] && e[i].flow)
				dep[v] = dep[u] + 1, q.push(v);
		}
	}
	return (bool) dep[t];
}

int dfs(int u, int t, int lim)
{
	if(u == t) return lim;
	int flow = 0;
	for(int i = cur[u]; i && flow < lim; i = e[i].nxt)
	{
		int v = e[i].to;
		cur[u] = i;
		if(dep[v] == dep[u] + 1 && e[i].flow)
		{
			int w = dfs(v, t, min(lim - flow, e[i].flow));
			e[i].flow -= w;
			e[i ^ 1].flow += w;
			flow += w;
		}
	}
	return flow;
}

int dinic(int s, int t)
{
	int maxflow = 0;
	while(bfs(s, t))
	{
		int flow = dfs(s, t, 1 << 30);
		maxflow += flow;
	}
	return maxflow;
}

2. 最小割

最小割=最大流。

常常用最小割表示损失。


3. 拆点

作用:


4. 最大权闭合子图

4.1. 定义

闭合图:对于一个有向图 \(G\),存在点集合 \(V\),任取点 \(u\) 属于 \(V\)\(u\) 的出边的另一个点也属于 \(V\),则为闭合图。即随便一个起点,对应的终点就是没有出度的点。
最大权闭合子图:当每个点有一个权值 \(w\)(有正有负),点权和最大的闭合图为最大权闭合子图。

选择 \(x\) 获得 \(v_x\) 的价值,选择 \(y\) 获得 \(v_y\) 的价值,\(x, y\) 都选有 \(d_{x, y}\) 的奖励。

通常使用最小割解决。

P4174 [NOI2006] 最大获利

4.2. 建图

  1. 增加源 \(S\)\(T\).
  2. 将原题中的边和点都看成事件,构造性地,将边转化为点事件,原图便转化为一个二分图。
  3. \(S\) 连接原图的正权点,容量为相应点权。
  4. 原图的负权点连接汇 \(t\),容量为相应点权的相反数。在此处,由于要求的是最小割即损失,“奖励”就变为了负权。
  5. 原图边的容量设为 \(inf\).

4.3. 答案

求出最大收益就是让损失最小。
把所有收益加起来,再减去最小割的损失,就是最大净收益。
\(ans = sum - maxflow\).

4.4. EX 最大权闭合子图

选择 \(x\) 获得 \(v_x\) 的价值,选择 \(y\) 获得 \(v_y\) 的价值,选 \(x\) 不选 \(y\)\(d_{x, y}\) 的惩罚。

建图方法:

  1. 增加源 \(S\)\(T\).
  2. \(S\) 连接原图的正权点,容量为相应点权。
  3. 原图的负权点连接汇 \(t\),容量为相应点权的相反数。
  4. 原图边的容量设为 \(d_{x, y}\).

P3872 [TJOI2010] 电影迷


5. 网格/地图型网络流

P2472 [SCOI2007] 蜥蜴

P3866 [TJOI2009] 战争游戏

P2172 [国家集训队] 部落战争

对于 \(n \times m\) 的网格,通常要写 \(id(i, j) = (i - 1) \times m + j\) 转化坐标.

大致是 \(S \to X_1 \to X_2 \to Y_1 \to Y_2 \to \cdots \to T\).
\(X_1 \to X_2\) 的流量可以存节点 \(X\) 的某种性质。


6. 费用流

在最大流的基础上,每条边多了一个单位费用 \(c\),表示流过一单位的流需要花费 \(c\).
问流最大时,费用最小是多少。

建边:\((u, v, flow, c), (v, u, 0, -c)\).

考虑在 EK 算法中动态维护源点到 \(u\) 的最短路 \(dis_u\).

我们有 \(dis_v = \min(dis_v, dis_u + w(u, v))\).

可以使用 SPFA 解决(因为有负权边,所以不能用 Dijkstra)。

P3381 【模板】最小费用最大流

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 5e3 + 5, M = 2e5 + 5, inf = 1e18;

int n, m, s, t, u, v, w, c, head[N], cur[N], maxflow, mincost, tot = 1, cyf[N], pre[N], dis[N];
bool vis[N];

struct edge
{
	int to, nxt, flow, cost;
} e[M << 1];

void addedge(int u, int v, int w, int c)
{
	e[++tot] = {v, head[u], w, c}, head[u] = tot;
}

bool spfa(int s, int t)
{
	queue<int> q;
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	q.push(s), dis[s] = 0, vis[s] = 1, cyf[s] = inf;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		for(int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if(dis[v] > dis[u] + e[i].cost && e[i].flow)
			{
				dis[v] = dis[u] + e[i].cost, pre[v] = i;
				cyf[v] = min(cyf[u], e[i].flow);
				if(!vis[v]) vis[v] = 1, q.push(v);
			}
		}
	}
	return (dis[t] < inf);
}

void EK(int s, int t)
{
	while(spfa(s, t))
	{
		int cur = t;
		maxflow += cyf[t];
		mincost += cyf[t] * dis[t];
		while(cur != s)
		{
			int pr = pre[cur];
			e[pr].flow -= cyf[t];
			e[pr ^ 1].flow += cyf[t];
			cur = e[pr ^ 1].to;
		}
	}
	return;
}

signed main()
{
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m >> s >> t;
	for(int i = 1; i <= m; i++)
	{
		cin >> u >> v >> w >> c;
		addedge(u, v, w, c);
		addedge(v, u, 0, -c);
	}
	EK(s, t);
	cout << maxflow << ' ' << mincost << '\n';
	return 0;
}

7. 无源汇上下界可行流

模型:一个网络,求出一个流,使得每条边的流量必须在 \([L_i, R_i]\) 内,每个点必须满足 \(\text{总流入量}=\text{总流出量}\)(流量守恒)。

  1. 令每条边流量为流量下限,建出这个图的残量网络,每条边的 \(\text{流量}=\text{流量上限}-\text{流量下限}\)
  2. 求出原图不满足流量守恒的附加流,附加流与原图合并后流量守恒。设 \(deg_i\) 表示原图(而不是残量网络)中 \(\text{流入流量}-\text{流出流量}\),对于附加流来说:\(deg_i=\text{流出流量}-\text{流入流量}\).
  3. 对于不平衡的流量,我们建一个全新的源点 \(S'\) 和全新的汇点 \(T'\).
  4. 对于 \(deg_i > 0\),从 \(S'\)\(i\) 建一条容量为 \(|deg_i|\) 的边;
    对于 \(deg_i < 0\),从 \(i\)\(T'\) 建一条容量为 \(|deg_i|\) 的边。

8. 有源汇上下界可行流

\(T\)\(S\) 连一条容量为 \(inf\) 的边,让流量循环一下,这就不平衡了。

接下来跑无源汇有上下界可行流模板即可。

9. 有源汇上下界最大流

  1. 跑一个有源汇上下界可行流,如果满流则设其答案为 \(flow_1\),否则无解。
  2. 删去从原图汇点到原图源点的边(即边 \((T, S)\))。
  3. 跑从原图源点到原图汇点的最大流,设答案为 \(flow_2\),则总答案 \(ans = flow_1 + flow_2\).

P5192 Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流

posted @ 2024-06-27 12:34  心灵震荡  阅读(6)  评论(0编辑  收藏  举报