网络流学习笔记
网络流
何为网络流
想要弄清楚网络流,首先要知道网络的概念,通常在运筹学中,网络是指一个有向图
网络流的性质
1.容量限制
2.斜对称性
3.流守恒性
事实上,网络流的应用已遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等众多领域。
网络流的常见问题
最大流
Ford-Fulkerson增广
退流操作带来的「抵消」效果使得我们无需担心我们按照「错误」的顺序选择了增广路。
接下类就是对Ford-Fulkerson增广算法的实现了,对于 Ford–Fulkerson 增广的不同实现,时间复杂度也各不相同。其中较主流的实现有 Edmonds–Karp, Dinic, SAP, ISAP 等算法,我会将自己理解了一些的算法介绍出来,至于其他的,下次一定(doge。
EK算法(Edmonds–Karp)
算法思想
EK算法就是通过BFS不断寻找增广路并不断更新最大流,直至在网络中再也找不到增广路为止。上面讲过,仅仅进行增广操作的话,最后得到的答案是错误的,因此需要退流,而为了追求速度,我们希望能在最短时间找到反向边,我们发现,利用邻接矩阵可以快速的找到反向边,因为邻阶矩阵就具有对称性,但是因为邻接矩阵相当于一个二维数组,无论是空间上还是时间上都不是很好,因此在网络流中的通常使用链式前向星来存储。其中,一个常用的技巧是,我们令边从偶数(通常为 0)开始编号,并在加边时总是紧接着加入其反向边使得它们的编号相邻。由此,我们可以令编号为
存边代码如下:
inline void addedge(int u,int v,int c){
edge[++tot].c = c;
edge[tot].to = v;
edge[tot].from = head[u];
head[u] = tot;
edge[++tot].c = 0;
edge[tot].to = u;
edge[tot].from = head[v];
head[v] = tot;
}
更新代码如下:
inline void update(){
int x = t;
while (x != s){
int v = pre[x];
edge[v].c -= dis[t]; //c是剩余容量
edge[v^1].c += dis[t];
x = edge[v^1].to;
}
ans += dis[t];
}
适用范围
时间复杂度为
完整代码
#include <bits/stdc++.h>
#define MAXN 505050
#define int long long
using namespace std;
inline int read(){
int x = 0,f = 1;
char c = getchar();
while (!isdigit(c)){
if (c == '-'){
f = -1;
}
c = getchar();
}
while (isdigit(c)){
x = x*10+c-'0';
c = getchar();
}
return x*f;
}
int n,m,s,t;
int tot = 1,vis[MAXN],pre[MAXN],head[MAXN],flag[2500][2500];
int dis[MAXN];
int ans;
struct node {
int from,to,c,flow;
}edge[MAXN];
inline void addedge(int u,int v,int c){
edge[++tot].to = v;
edge[tot].from = head[u];
edge[tot].c = c;
head[u] = tot;
edge[++tot].to = u;
edge[tot].from = head[v];
edge[tot].c = 0;
head[v] = tot;
}
inline bool bfs(){
for (int i=1;i<=n;i++){
vis[i] = 0;
}
queue<int> q;
q.push(s);
vis[s] = 1;
dis[s] = 2005020600;
while (!q.empty()){
int x = q.front();
q.pop();
for (int i=head[x];i;i=edge[i].from){
int v = edge[i].to;
if (!edge[i].c) continue;
if (vis[v]) continue;
dis[v] = min(dis[x],edge[i].c);
pre[v] = i;
q.push(v);
vis[v] = 1;
if (v == t)return 1;
}
}
return 0;
}
inline void update(){
int x = t;
while (x!=s){
int v = pre[x];
edge[v].c-=dis[t];
edge[v^1].c+=dis[t];
x = edge[v^1].to;
}
ans+=dis[t];
}
signed main(){
n = read(),m = read(),s = read(),t = read();
for (int i=1;i<=m;i++){
int u = read(),v = read(),w = read();
if (flag[u][v] == 0){
addedge(u,v,w);
flag[u][v] = tot;
}
else {
edge[flag[u][v]-1].c+=w;
}
}
while (bfs()!=0){
update();
}
cout<<ans<<'\n';
return 0;
}
Dinic算法
Dinic算法在大部分图上的效率都十分优秀,因此也算是处理网络流问题中较为常见的,其时间复杂度是
算法思想
EK算法可能会遍历整个残量网络(所有边均为其残量的
这时候就要我们的Dinic算法出马了。
分层图&
考虑在进行增广前先对网络进行
算法框架
1.在残量网络上BFS求出节点的层次,构造分层图
2.在分层图上DFS寻找增广路,在回溯时同时更新边权
适用范围
时间复杂度为
同时,Dinic算法还能用来求二分图,复杂度比匈牙利算法低,但还没学,所以在这就不介绍了。
代码
#include <bits/stdc++.h>
using namespace std;
const long long inf=2005020600;
int n,m,s,t,u,v;
long long w,ans,dis[520010];
int tot=1,now[520010],head[520010];
struct node {
int to,net;
long long val;
} e[520010];
inline void add(int u,int v,long long w) {
e[++tot].to=v;
e[tot].val=w;
e[tot].net=head[u];
head[u]=tot;
e[++tot].to=u;
e[tot].val=0;
e[tot].net=head[v];
head[v]=tot;
}
inline int bfs() { //在惨量网络中构造分层图
for(register int i=1;i<=n;i++) dis[i]=inf;
queue<int> q;
q.push(s);
dis[s]=0;
now[s]=head[s];
while(!q.empty()) {
int x=q.front();
q.pop();
for(register int i=head[x];i;i=e[i].net) {
int v=e[i].to;
if(e[i].val>0&&dis[v]==inf) {
q.push(v);
now[v]=head[v];
dis[v]=dis[x]+1;
if(v==t) return 1;
}
}
}
return 0;
}
inline int dfs(int x,long long sum) { //sum是整条增广路对最大流的贡献
if(x==t) return sum;
long long k,res=0; //k是当前最小的剩余容量
for(register int i=now[x];i&∑i=e[i].net) {
now[x]=i; //当前弧优化
int v=e[i].to;
if(e[i].val>0&&(dis[v]==dis[x]+1)) {
k=dfs(v,min(sum,e[i].val));
if(k==0) dis[v]=inf; //剪枝,去掉增广完毕的点
e[i].val-=k;
e[i^1].val+=k;
res+=k; //res表示经过该点的所有流量和(相当于流出的总量)
sum-=k; //sum表示经过该点的剩余流量
}
}
return res;
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
for(register int i=1;i<=m;i++) {
scanf("%d%d%lld",&u,&v,&w);
add(u,v,w);
}
while(bfs()) {
ans+=dfs(s,inf); //流量守恒(流入=流出)
}
printf("%lld",ans);
return 0;
}
费用流
费用流就是最小费用最大流,意思就是在之前的情景下,对于每一根管道,我们都给他加上一个代价,就是每经过一条管道都会花费相应的费用,然后你要在这其中找到一条保证最小花费的最大流。
聪明的读者是不是已经大概知道怎么做了呢
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理