图论-zkw费用流

模板

这是一个求最小费用最大流的算法,因为发明者是神仙zkw,所以叫zkw费用流(就是zkw线段树那个zkw)。有些时候比EK快,有些时候慢一些,没有比普通费用流算法更难,所以学zkw费用流之前,不需要先掌握普通费用流。

前置知识:\(\texttt{网络最大流}\)。

在学了网络最大流后,如果在没条边上加个限制,就是 \(cost\),表示这条边上每走过 \(1\) 流量就要付费 \(cost\),求在最大流的情况下,要交的最少费用



如上费用流图,最大流为 \(1\),在达到最大流的同时,最小费用为 \(3\)。最小费用路径:\(s\to1\to2\to t\),\(1\times (2+0+1)=3\)。

像这种图,普通费用流算法更快,因为它一次只增广一条路径。但一些大的费用流图往往最大流要经过好多路径,那么zkw费用流快得多。那么为什么还要有EK的存在呢?因为有些毒瘤题专门卡zkw费用流(路径长,最大流路径单一)。


接下来开始讲zkw费用流的实现。 首先凡是网络流题,都是要建双向边的,要不然就“走不了回头路”了。对于费用流,走回头路可以把钱要回来,所以建边如下:

class Graph{
public:
int top,to[M<<1],fw[M<<1],ct[M<<1];
//top表示边数,因为后面要把互为反边的边通过^1获得,所以正边的编号要为偶数,然后负边的编号要为正边+1
//to表示这条边通往的节点,fw表示边的流量,ct表示边的费用
vector<int> g[V];//比较奇怪的建边方式
Graph(){top=1;}
void add(int x,int y,int f,int c){
g[x].push_back(++top);
to[top]=y,fw[top]=f,ct[top]=c;
}
void Add(int x,int y,int f,int c){
add(x,y,f,c),add(y,x,0,-c);
//如同最大流,建反边,钱可以走反边要回来,所以反边费用为-c
}
};

然后开始做最小费用最大流。先看看求出答案的框架:

void mcmf(){
while(spfa()){
vis[t]=1;
while(vis[t]){
for(int i=1;i<=p;i++) vis[i]=0;
fans+=dfs(s,inf);
}
}
}
//fans表示最大流,cans表示最小费用,在dfs中求

然后来看这个 \(\texttt{spfa()}\) 他复活了,以边的费用为边权,从 \(t\) 到 \(s\) 反向跑最短路。这个 \(\texttt{spfa()}\) 有 \(3\) 个作用:

1.把图的点层次分出来,使增广有秩序。

2.把图的最短路跑出来,保证最小费用。

3.把图的连通性算出来,以便抉择增广。

我也不知道为什么作用这么整齐,代码:

bool spfa(){
//vis维护spfa,dep表示连通性+点层次+最短路
for(int i=1;i<=p;i++) vis[i]=0,dep[i]=inf;
q.push_back(t),vis[t]=1,dep[t]=0;
while(q.size()){
int x=q.front();q.pop_front(),vis[x]=0;
for(auto i:g[x])if(fw[i^1]&&dep[to[i]]>dep[x]-ct[i]){
dep[to[i]]=dep[x]-ct[i]; //dep表示最短路
if(!vis[to[i]]){
vis[to[i]]=1;
if(q.size()&&dep[to[i]]<dep[q.front()])
q.push_front(to[i]);
else q.push_back(to[i]); //SLF优化
}
}
}
return dep[s]<inf;//dep表示联通性
}

然后是增广的 \(\texttt{dfs(x,F)}\)。这就是zkw费用流和EK的区别之所在。与 \(\texttt{spfa()}\) 不同,这是正着跑图,所以就可以避免 \(dep[]\) 每次增广前都必须重新算的浪费时间。代码:

int dfs(int x,int F){
vis[x]=1; //这里的vis表示联通性,把上面的vis数组清空后回收利用
if(x==t||!F) return F;//如果到终点或者没流量了,逃。
int f,flow=0;
for(auto i:g[x])if(!vis[to[i]]&&fw[i]&&dep[x]-ct[i]
==dep[to[i]]&&(f=dfs(to[i],min(F,fw[i])))>0){
//如果节点没走过,这条边有流量,dep判断层次,保证是低层次向高层次增广,f为递归得到的这条边实际能流的流量
cans+=f*ct[i],fw[i]-=f,fw[i^1]+=f,flow+=f,F-=f; //流,减去正边流量,增加反边可反悔流量,增加一次增广最大流流量,减少来时剩下的流量
if(!F) break; //优化
}
return flow;
}

之所以算 \(cans\) 的时候只需 \(cans+=f\times ct[i]\) 是因为一条增广的流肯定是处处流量相等的。

然后再回来看总体求最小费用最大流的函数,一次 \(\texttt{spfa()}\) 里就可以增广很多路,通过 \(vis[t]\) 判断 \(s\) 到 \(t\) 的流量这次是否流光,即判断 \(s\) 和 \(t\) 的连通性。代码:

void mcmf(){
while(spfa()){
vis[t]=1;
while(vis[t]){
for(int i=1;i<=p;i++) vis[i]=0;
fans+=dfs(s,inf);
}
}
}

总体上,zkw费用流的码量与EK是等同的,但大部分时候快很多。如果你理解了zkw费用流,那么蒟蒻就放代码了:

#include <bits/stdc++.h>
using namespace std;
const int V=1e6;
const int M=3e6;
const int inf=0x3f3f3f3f;
int n,m,s,t,p,fans,cans;
class Graph{
public:
int top,to[M<<1],fw[M<<1],ct[M<<1];
vector<int> g[V];
Graph(){top=1;}
void add(int x,int y,int f,int c){
g[x].push_back(++top);
to[top]=y,fw[top]=f,ct[top]=c;
}
void Add(int x,int y,int f,int c){
add(x,y,f,c),add(y,x,0,-c);
}
};
class zkwMCMF:public Graph{
public:
int dep[V]; bool vis[V];
deque<int> q;
bool spfa(){
for(int i=1;i<=p;i++) vis[i]=0,dep[i]=inf;
q.push_back(t),vis[t]=1,dep[t]=0;
while(q.size()){
int x=q.front();q.pop_front(),vis[x]=0;
for(auto i:g[x])if(fw[i^1]&&dep[to[i]]>dep[x]-ct[i]){
dep[to[i]]=dep[x]-ct[i];
if(!vis[to[i]]){
vis[to[i]]=1;
if(q.size()&&dep[to[i]]<dep[q.front()])
q.push_front(to[i]);
else q.push_back(to[i]);
}
}
}
return dep[s]<inf;
}
int dfs(int x,int F){
vis[x]=1;
if(x==t||!F) return F;
int f,flow=0;
for(auto i:g[x])if(!vis[to[i]]&&fw[i]&&dep[x]-ct[i]
==dep[to[i]]&&(f=dfs(to[i],min(F,fw[i])))>0){
cans+=f*ct[i],fw[i]-=f,fw[i^1]+=f,flow+=f,F-=f;
if(!F) break;
}
return flow;
}
void mcmf(){
while(spfa()){
vis[t]=1;
while(vis[t]){
for(int i=1;i<=p;i++) vis[i]=0;
fans+=dfs(s,inf);
}
}
}
}network;
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t),p=n;
for(int i=1,x,y,f,c;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&f,&c);
network.Add(x,y,f,c);
}
network.mcmf();
printf("%d %d\n",fans,cans);
return 0;
}

最后附上我用EK和zkw费用流提交模板的用时比较:

\(\texttt{EK:}\texttt{1.76s}\)



\(\texttt{zkw费用流:}\color{#44cc44}{\texttt{520ms}}\)



整整快四倍!

祝大家学习愉快!

图论-zkw费用流的更多相关文章

  1. zkw费用流+当前弧优化

    zkw费用流+当前弧优化 var o,v:..] of boolean; f,s,d,dis:..] of longint; next,p,c,w:..] of longint; i,j,k,l,y, ...

  2. 学习了ZKW费用流

    所谓ZKW费用流,其实就是Dinic. 若干年前有一个人发明了最小增广路算法,每次用BFS找一条增广路,时间O(nm^2) 然后被DinicD飞了:我们为什么不可以在长度不变时多路增广呢?时间O(n^ ...

  3. zkw费用流

    期末结束,竞赛生活继续开始,先怒刷完寒假作业再说 至于期末考试,数学跪惨,各种哦智障错,还有我初中常用的建系大法居然被自己抛至脑后,看来学的还是不扎实,以后数学要老老实实学.物理被永哥黑了两分,然后很 ...

  4. 【zkw费用流】[网络流24题]餐巾计划问题

    题目描述 一个餐厅在相继的N天里,第i天需要Ri块餐巾(i=l,2,-,N).餐厅可以从三种途径获得餐巾. (1)购买新的餐巾,每块需p分: (2)把用过的餐巾送到快洗部,洗一块需m天,费用需f分(f ...

  5. CSU 1948: 超级管理员(普通费用流&&zkw费用流)

    Description 长者对小明施加了膜法,使得小明每天起床就像马丁的早晨一样. 今天小明早上醒来发现自己成了一位仓管员.仓库可以被描述为一个n × m的网格,在每个网格上有几个箱子(可能没有).为 ...

  6. BZOJ2673 [Wf2011]Chips Challenge 费用流 zkw费用流 网络流

    https://darkbzoj.cf/problem/2673 有一个芯片,芯片上有N*N(1≤N≤40)个插槽,可以在里面装零件. 有些插槽不能装零件,有些插槽必须装零件,剩下的插槽随意. 要求装 ...

  7. 图论:费用流-SPFA+EK

    利用SPFA+EK算法解决费用流问题 例题不够裸,但是还是很有说服力的,这里以Codevs1227的方格取数2为例子来介绍费用流问题 这个题难点在建图上,我感觉以后还要把网络流建模想明白才能下手去做这 ...

  8. zkw费用流 学习笔记

    分析 记\(D_i\)为从\(S\)出发到\(i\)的最短路 最短路算法保证, 算法结束时 对于任意存在弧\((i,j)\)满足\(D_i + c_{ij}\ge D_j\) ① 且对于每个 \(j\ ...

  9. P4015 运输问题【zkw费用流】

    输入输出样例 输入 #1复制 2 3 220 280 170 120 210 77 39 105 150 186 122 输出 #1复制 48500 69140zuixiaofeiyo 说明/提示 1 ...

随机推荐

  1. kafak ack应答机制

    ack 应答机制 对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失, 所以没必要等 ISR 中的 follower 全部接收成功. 所以 Kafka 为用户提供了三种可靠性级 ...

  2. python之路 《六》函数

    ---恢复内容开始--- 为什么要有函数? 当你的老板要你写一个程序 1 def 函数0(): 2 # 如果cpu占用率>90 3 # 发送邮件 4 # 发出警报 5 6 def 函数1(): ...

  3. 从执行上下文角度重新理解.NET(Core)的多线程编程[1]:基于调用链的”参数”传递

    线程是操作系统能够进行运算调度的最小单位,操作系统线程进一步被封装成托管的Thread对象,手工创建并管理Thread对象已经成为了所能做到的对线程最细粒度的控制了.后来我们有了ThreadPool, ...

  4. 13.java设计模式之模板模式

    基本需求: 制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎 通过添加不同的配料,可以制作出不同口味的豆浆 选材.浸泡和放到豆浆机打碎这几个步骤对于制作每种口味 ...

  5. Guitar Pro使用技巧之乐段回放练习

    Guitar Pro中的"回放"功能是我们在吉他练习中非常常用的一项功能.我们在吉他练习中碰到某一乐段比较练习比较困难时,我们就可以用鼠标在Guitar Pro上选中该乐段,然后进 ...

  6. 【移动自动化】【一】环境依赖:android sdk 环境配置(windows + linux)

    Android自动化前提依赖 android sdk 模拟器: mumu模拟器, 逍遥模拟器 真机 windows 环境下Android SDK 配置 配置java环境 去官网下载jdk http:/ ...

  7. Python基础整理,懒得分类了,大家对付看看吧

    第一次搞这么多图

  8. 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

    (这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...

  9. ZAB

    ZAB=ZooKeeper Atomic Broadcast ZooKeeper原子消息广播协议,支持崩溃回复的原子广播协议. zk使用一个单一的主进程来接受并处理客户端的所有事务请求,并采用ZAB的 ...

  10. zk特性

    看了又忘系列: 1.zk会将全量的数据存储在内存中,以此来实现提高服务器吞吐,减少延迟的目的. 2.集群中每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都相互保持着通信.只要集群中存在超过 ...