网络流笔记
网络流笔记
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. 拆点
作用:
- 限制点出现个数:P2764 最小路径覆盖问题
- 点本身有不同的性质:
P4662 [BalticOI 2008] 黑手党
将一个节点 X 拆成 X1 和 X2,从而将 X 的点权转移到边 (X1,X2) 上。
4. 最大权闭合子图
4.1. 定义
闭合图:对于一个有向图 G,存在点集合 V,任取点 u 属于 V,u 的出边的另一个点也属于 V,则为闭合图。即随便一个起点,对应的终点就是没有出度的点。
最大权闭合子图:当每个点有一个权值 w(有正有负),点权和最大的闭合图为最大权闭合子图。
选择 x 获得 vx 的价值,选择 y 获得 vy 的价值,x,y 都选有 dx,y 的奖励。
通常使用最小割解决。
4.2. 建图
- 增加源 S 汇 T.
- 将原题中的边和点都看成事件,构造性地,将边转化为点事件,原图便转化为一个二分图。
- S 连接原图的正权点,容量为相应点权。
- 原图的负权点连接汇 t,容量为相应点权的相反数。在此处,由于要求的是最小割即损失,“奖励”就变为了负权。
- 原图边的容量设为 inf.
4.3. 答案
求出最大收益就是让损失最小。
把所有收益加起来,再减去最小割的损失,就是最大净收益。
ans=sum−maxflow.
4.4. EX 最大权闭合子图
选择 x 获得 vx 的价值,选择 y 获得 vy 的价值,选 x 不选 y 有 dx,y 的惩罚。
建图方法:
- 增加源 S 汇 T.
- S 连接原图的正权点,容量为相应点权。
- 原图的负权点连接汇 t,容量为相应点权的相反数。
- 原图边的容量设为 dx,y.
5. 网格/地图型网络流
对于 n×m 的网格,通常要写 id(i,j)=(i−1)×m+j 转化坐标.
大致是 S→X1→X2→Y1→Y2→⋯→T.
X1→X2 的流量可以存节点 X 的某种性质。
6. 费用流
在最大流的基础上,每条边多了一个单位费用 c,表示流过一单位的流需要花费 c.
问流最大时,费用最小是多少。
建边:(u,v,flow,c),(v,u,0,−c).
考虑在 EK 算法中动态维护源点到 u 的最短路 disu.
我们有 disv=min(disv,disu+w(u,v)).
可以使用 SPFA 解决(因为有负权边,所以不能用 Dijkstra)。
#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] 内,每个点必须满足 总流入量=总流出量(流量守恒)。
- 令每条边流量为流量下限,建出这个图的残量网络,每条边的 流量=流量上限−流量下限;
- 求出原图不满足流量守恒的附加流,附加流与原图合并后流量守恒。设 degi 表示原图(而不是残量网络)中 流入流量−流出流量,对于附加流来说:degi=流出流量−流入流量.
- 对于不平衡的流量,我们建一个全新的源点 S′ 和全新的汇点 T′.
- 对于 degi>0,从 S′ 到 i 建一条容量为 |degi| 的边;
对于 degi<0,从 i 到 T′ 建一条容量为 |degi| 的边。
8. 有源汇上下界可行流
从 T 到 S 连一条容量为 inf 的边,让流量循环一下,这就不平衡了。
接下来跑无源汇有上下界可行流模板即可。
9. 有源汇上下界最大流
- 跑一个有源汇上下界可行流,如果满流则设其答案为 flow1,否则无解。
- 删去从原图汇点到原图源点的边(即边 (T,S))。
- 跑从原图源点到原图汇点的最大流,设答案为 flow2,则总答案 ans=flow1+flow2.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!