dinic及当前弧优化

Posted ticmis

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dinic及当前弧优化相关的知识,希望对你有一定的参考价值。

网络流 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及当前弧优化的主要内容,如果未能解决你的问题,请参考以下文章

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

P3376 (最大流 dinic)

DINIC网络流+当前弧优化

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

网络流之当前弧优化浅谈

CODEVS1993 草地排水