网络流笔记

网络流笔记

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 属于 Vu 的出边的另一个点也属于 V,则为闭合图。即随便一个起点,对应的终点就是没有出度的点。
最大权闭合子图:当每个点有一个权值 w(有正有负),点权和最大的闭合图为最大权闭合子图。

选择 x 获得 vx 的价值,选择 y 获得 vy 的价值,x,y 都选有 dx,y 的奖励。

通常使用最小割解决。

P4174 [NOI2006] 最大获利

4.2. 建图

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

4.3. 答案

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

4.4. EX 最大权闭合子图

选择 x 获得 vx 的价值,选择 y 获得 vy 的价值,选 x 不选 ydx,y 的惩罚。

建图方法:

  1. 增加源 ST.
  2. S 连接原图的正权点,容量为相应点权。
  3. 原图的负权点连接汇 t,容量为相应点权的相反数。
  4. 原图边的容量设为 dx,y.

P3872 [TJOI2010] 电影迷


5. 网格/地图型网络流

P2472 [SCOI2007] 蜥蜴

P3866 [TJOI2009] 战争游戏

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

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

大致是 SX1X2Y1Y2T.
X1X2 的流量可以存节点 X 的某种性质。


6. 费用流

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

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

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

我们有 disv=min(disv,disu+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. 无源汇上下界可行流

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

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

8. 有源汇上下界可行流

TS 连一条容量为 inf 的边,让流量循环一下,这就不平衡了。

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

9. 有源汇上下界最大流

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

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

posted @   心灵震荡  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示