网络流 dinic及当前弧优化

前言

dinic比较适合学习完km之后再学习。因为dinic感觉像是km的一种优化。总之难度不是特别大

dinic算法

好了,言归正传。先分析一下km为什么效率低下?因为km每一次寻找maxflow,就确确实实只会找一条增广路。但事实上,一个图可能会同时有多条增广路,假如能同时更新一批增广路,肯定就会比一次更新一条快嘛

那么怎么判是否存在增广路?

沿用km的思想,从一个点开始bfs。只是这一次就不再需要记录flow值,因为真正的解决增广路的过程在于后面的增广路。可是,假如单单bfs一遍,最后还是要完整的跑一遍dfs感觉有些浪费时间。明明已经bfs了一遍全部节点,那莫不如把bfs的深度记录下来,这样就按照dep严格加一的顺序dfs下来,一定(若路径上的边权全部为正值)会导向汇点。

好了,bfs之后,怎么dfs妮?

从源点开始,记录这个点的节点编号和可以流过的最大值。dfs每一条可行的边,然后更新即可。由于dfs是递归的嘛,所以也不再需要pre以记录前驱,只需要回溯的时候沿途更更新就好。

一个简洁的模板:

while(bfs()) ans+=dfs(src,maxflow);

完整模板:


#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std; const int MAX=1e5+5,INF=0x3f3f3f3f;
int n,m,src,tar;
int edge[MAX<<1],head[MAX],nxt[MAX<<1],wei[MAX<<1];
int flow[MAX],pre[MAX],cur[MAX],dep[MAX]; void insert(int,int,int,int);
int dinic();
bool bfs(int);
int dfs(int,int); int main(){
freopen("test.in","r",stdin); cin>>n>>m>>src>>tar;
for(int i=1;i<=m;++i){
int u,v,w; cin>>u>>v>>w;
insert(u,v,w,(i<<1)); insert(v,u,0,(i<<1)+1);
} cout<<dinic()<<endl; return 0;
} void insert(int from,int to,int w,int id){
edge[id]=to; nxt[id]=head[from]; head[from]=id; wei[id]=w;
} int dinic(){
int ans=0;
while(bfs(src)) ans+=dfs(src,INF);
return ans;
} bool bfs(int s){
int line[MAX],l=0,r=1; line[r]=s;
memset(dep,0,sizeof(dep));
dep[s]=1;
while(l<r){
int u=line[++l];
for(int i=head[u];i;i=nxt[i]){
int v=edge[i];
if(!wei[i]||dep[v]) continue;
dep[v]=dep[u]+1;
line[++r]=v;
}
}
return dep[tar];
} int dfs(int u,int flow){
if(u==tar||!flow) return flow;
int ans=0;
for(int i=head[u];i;i=nxt[i]){
int v=edge[i]; if(dep[v]!=dep[u]+1) continue;
int add=dfs(v,min(flow,wei[i]));
if(add){
flow-=add;
wei[i]-=add;
wei[i^1]+=add;
ans+=add;
}
}
return ans;
}

当前弧优化

貌似,dinic中,只有一遍dfs到头就返回的那种流派才用得上。像上文所说的那样做法,加了优化反而被劣化了??

dinic自身还有一个优化。由于dinic有一个dfs,而且还是一个TAG图,那么一个点就有可能走了多次。对一个点的dfs,当它从一条边转换到了另外一条边时,意味着上一条边已经被“榨干”,也就是说从当前bfs下,这个点再次走这条边不会再找到增广路了,重复走只会浪费时间。

当前弧优化就是记录一个点上次dfs走到了哪儿的玩意儿。将cur作为head,直接从cur开始dfs即可,之前走过的边不再需要访问。

只需再原来的基础上,加上一个cur的初始化:

for(int i=1;i<=n;++i) cur[i]=head[i];

以及实时更新:

for(int i=cur[u];i;i=nxt[i]){
cur[i]=i;
...
}

完整代码:


#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std; const int MAX=1e5+5,INF=0x3f3f3f3f;
int n,m,src,tar;
int edge[MAX<<1],head[MAX],nxt[MAX<<1],wei[MAX<<1];
int flow[MAX],pre[MAX],cur[MAX],dep[MAX]; void insert(int,int,int,int);
int dinic();
bool bfs(int);
int dfs(int,int); int main(){
//freopen("test.in","r",stdin); cin>>n>>m>>src>>tar;
for(int i=1;i<=m;++i){
int u,v,w; cin>>u>>v>>w;
insert(u,v,w,(i<<1)); insert(v,u,0,(i<<1)+1);
} cout<<dinic()<<endl; return 0;
} void insert(int from,int to,int w,int id){
edge[id]=to; nxt[id]=head[from]; head[from]=id; wei[id]=w;
} int dinic(){
int ans=0;
while(bfs(src)) ans+=dfs(src,INF);
return ans;
} bool bfs(int s){
int line[MAX],l=0,r=1; line[r]=s;
memset(dep,0,sizeof(dep));
dep[s]=1;
while(l<r){
int u=line[++l];
for(int i=head[u];i;i=nxt[i]){
int v=edge[i];
if(!wei[i]||dep[v]) continue;
dep[v]=dep[u]+1;
line[++r]=v;
}
}
if(dep[tar]){
for(int i=1;i<=n;++i) cur[i]=head[i];
return true;
}
else return false;
} int dfs(int u,int flow){
if(u==tar||!flow) return flow;
int ans=0;
for(int i=cur[u];i;i=nxt[i]){
cur[u]=i;
int v=edge[i]; if(dep[v]!=dep[u]+1) continue;
int add=dfs(v,min(flow,wei[i]));
if(add){
flow-=add;
wei[i]-=add;
wei[i^1]+=add;
ans+=add;
}
}
return ans;
}

upd 2019.4.28

近日有幸得到了kiana大佬的教学视频,做题的时候碰到了一道好题:P3227 [HNOI2013]切糕

洛谷上ac了,就没有注意具体用时。今天突然兴致勃发,想在内网上蹭一下刷题量,居然tle了两次。起初怀疑内网是不是太古老了,有什么禁忌,到最后发现,我的代码居然被大佬们好1600ms??

那就优化吧。用反复测试之后,确认了问题就是出在了dinic上

优化一

int add=dfs(v,min(w[i],flow));
//balabala
flow-=add; //here!! attentino!!

最后一句话之后要及时判断flow是否还有残留,如果没有的化及时break掉!

想不到吧,就这么一句话就能优化掉1100ms??

优化二

敬告:千万不要偷懒用memset!!

memset(dep,0,sizeof(dep));

改成

for(int i=1;i<=tar;++i) dep[i]=0;

之后,又优化掉了100ms!!

优化三

注意,此优化极端玄学,请勿频繁使用!!

那就是,吸氧。成功再次优化300ms,最终耗时302ms

优化版代码:

bool bfs(){
queue <int> line; line.push(src);
for(int i=1;i<=tar;++i) dep[i]=0; dep[src]=1;
while(!line.empty()){
int u=line.front(); line.pop();
for(int i=head[u];i;i=nxt[i]){
int v=edge[i]; if(!w[i]||dep[v]) continue;
dep[v]=dep[u]+1;
line.push(v);
}
}
if(dep[tar]){
for(int i=1;i<=tar;++i) cur[i]=head[i];
return true;
}
return false;
} int dfs(int u,int flow){
if(u==tar||!flow) return flow;
int ans=0;
for(int i=cur[u];i;i=nxt[i]){
cur[u]=i;
int v=edge[i]; if(dep[v]!=dep[u]+1) continue;
int add=dfs(v,min(flow,w[i]));
if(add){
flow-=add;
ans+=add;
w[i]-=add;
w[i^1]+=add;
if(!flow) break;
}
}
return ans;
} int dinic(){
int ans=0;
while(bfs()) ans+=dfs(src,INF);
return ans;
}

dinic及当前弧优化的更多相关文章

  1. DINIC网络流+当前弧优化

    DINIC网络流+当前弧优化 const inf=; type rec=record s,e,w,next:longint; end; var b,bb,d,q,tb:..] of longint; ...

  2. 最大流当前弧优化Dinic模板

    最大流模板: 普通最大流 无向图限制:将无向图的边拆成2条方向相反的边 无源汇点有最小流限制的最大流:理解为水管流量形成循环,每根水管有流量限制,并且流入量等于流出量 有源汇点的最小流限制的最大流 顶 ...

  3. 最大流当前弧优化Dinic分层模板

    最大流模板: 普通最大流 无向图限制:将无向图的边拆成2条方向相反的有向边 顶点有流量限制:拆成2个点,连接一条容量为点容量限制的边 无源汇点有最小流限制的最大流:理解为水管流量形成循环 有源汇点的最 ...

  4. 【最大流之Dinic算法】POJ1273 【 & 当前弧优化 & 】

    总评一句:Dinic算法的基本思想比较好理解,就是它的当前弧优化的思想,网上的资料也不多,所以对于当前弧的优化,我还是费了很大的功夫的,现在也一知半解,索性就写一篇博客,来发现自己哪里的算法思想还没理 ...

  5. ARC085E(最小割规划【最大流】,Dinic当前弧优化)

    #include<bits/stdc++.h>using namespace std;typedef long long ll;const ll inf=0x3f3f3f3f;int cn ...

  6. 解题报告:hdu 3572 Task Schedule(当前弧优化Dinic算法)

    Problem Description Our geometry princess XMM has stoped her study in computational geometry to conc ...

  7. Dinic当前弧优化 模板及教程

    在阅读本文前,建议先自学最大流的Ek算法. 引入 Ek的核心是执行bfs,一旦找到增广路就停下来进行增广.换言之,执行一遍BFS执行一遍DFS,这使得效率大大降低.于是我们可以考虑优化. 核心思路 在 ...

  8. [Poj2112][USACO2003 US OPEN] Optimal Milking [网络流,最大流][Dinic+当前弧优化]

    题意:有K个挤奶机编号1~K,有C只奶牛编号(K+1)~(C+K),每个挤奶机之多能挤M头牛,现在让奶牛走到挤奶机处,求奶牛所走的最长的一条边至少是多少. 题解:从起点向挤奶机连边,容量为M,从挤奶机 ...

  9. P3355 骑士共存问题 二分建图 + 当前弧优化dinic

    P3355 骑士共存问题 题意: 也是一个棋盘,规则是“马”不能相互打到. 思路: 奇偶点分开,二分图建图,这道题要注意每个点可以跑八个方向,两边都可以跑,所以边 = 20 * n * n. 然后di ...

  10. P3376 网络最大流模板(Dinic + dfs多路增广优化 + 炸点优化 + 当前弧优化)

    ### P3376 题目链接 ### 这里讲一下三种优化的实现以及正确性. 1.dfs多路增广优化 一般的Dinic算法中是这样的,bfs() 用于标记多条增广路,以至于能一次 bfs() 出多次 d ...

随机推荐

  1. import cv2报错

    其实是没错的,不过有的python编译器对这个不太支持,把import cv2 改为import cv2.cv2 as cv2就行了.

  2. 知识图谱顶会论文(SIGIR-2022) MorsE:归纳知识图嵌入的元知识迁移

    MorsE:归纳知识图嵌入的元知识迁移 论文题目: Meta-Knowledge Transfer for Inductive Knowledge Graph Embedding 论文地址: http ...

  3. Spark基本知识

    Spark基本知识 Spark 是一种基于内存的快速.通用.可扩展的大数据分析计算引擎. spark与hadoop的区别 Hadoop Hadoop 是由 java 语言编写的,在分布式服务器集群上存 ...

  4. 《吐血整理》高级系列教程-吃透Fiddler抓包教程(34)-Fiddler如何抓取微信小程序的包-上篇

    1.简介 有些小伙伴或者是童鞋们说小程序抓不到包,该怎么办了???其实苹果手机如果按照宏哥前边的抓取APP包的设置方式设置好了,应该可以轻松就抓到包了.那么安卓手机小程序就比较困难,不是那么友好了.所 ...

  5. C++实现真值表

    这一片文章主要是关于真值表,在完成之前我也遇到了许多问题.比如怎么去求解表达式的值,怎么去将每个变量进行赋值,也就是如何 将n个字符进行01全排列. 01全排列真的神奇,01全排列其实就是2^n.他可 ...

  6. [排序算法] 堆排序 (C++)

    堆排序解释 什么是堆 堆 heap 是一种近似完全二叉树的数据结构,其满足一下两个性质 1. 堆中某个结点的值总是不大于(或不小于)其父结点的值: 2. 堆总是一棵完全二叉树 将根结点最大的堆叫做大根 ...

  7. 基于Spring-AOP的自定义分片工具

    作者:陈昌浩 1 背景 随着数据量的增长,发现系统在与其他系统交互时,批量接口会出现超时现象,发现原批量接口在实现时,没有做分片处理,当数据过大时或超过其他系统阈值时,就会出现错误.由于与其他系统交互 ...

  8. C++初阶(stack+queue)

    stack stack介绍 stack是一种先进后出的数据结构,只有一个出口,类似于栈.stack容器哦允许新增元素,移除元素,取得栈顶元素,但是除了最顶端之后,没有任何其他办法可以存取stack的其 ...

  9. c++题目:切香肠

    c++题目:切香肠 题目 题目描述 有 n 条香肠,每条香肠的长度相等.我们打算将这些香肠切开后全部分给 k 名客人,且要求每名客人获得一样多的香肠.请问最少需要切几刀?注意一刀只能切断一条香肠,每个 ...

  10. 《Hierarchical Text-Conditional Image Generation with CLIP Latents》阅读笔记

    概括 模型总述 本篇论文主要介绍DALL·E 2模型,它是OpenAI在2022年4月推出的一款模型,OpenAI在2021年1月推出了DALL·E模型,2021年年底推出了GLIDE模型. DALL ...