首页 > 代码库 > maxflow1459dinic

maxflow1459dinic

题目大意:总共有n个节点,其中有发电站np个、用户nc个和调度器n-np-nc个三种节点,每个发电站有一个最大发电量,每个用户有个最大接受电量,现在有m条有向边,边有一个最大的流量代表,最多可以流出这么多电,现在从发电站发电到用户,问最多可以发多少电

解题思路:将发电站看成源点,用户看成汇点,这样求最大流就可以了,不过因为有多个源点和汇点,所以加一个超级源点指向所有的发电站,流量总量为发电站的流量总量,加一个超级汇点让所有的用户指向他,流量为用户的最大流量,这样,从超级源点到超级汇点求最大流即可。

 

 

关于dinic算法:EdmondsKarpBFS之后从终点开始按照pre域进行递减,dinicBFS之后从源点进行递归。

层次图是什么东西呢?层次,其实就是从源点走到那个点的最短路径长度。其实EdmondsKarp也是按照层次来获得最短路的,算法导论在证明按照最短路寻找增广路时,假设所有边权值为1. 

 

这样的话,在找到第一条增广路径后,只需要回溯到 点,就可以继续找下去了。

这样做的好处是,避免了找到一条路径就从头开始寻找另外一条的开销。

也就是再次从 寻找到 的开销。

这个过程看似复杂,但是代码实现起来很优雅,因为它的本质就是回溯!

下面给出一个dfs的图例,图中,红边代表找到的增广路p中的边。

 

 

 

/*

dinic

Memory 320K

Time  563MS

*/

#include <iostream>

#include <queue>

using namespace std;

#define min(a,b) (a<b?a:b)

#define MAXV 105

#define MAXINT INT_MAX

 

typedef struct{

int flow; //流量

int capacity; //最大容量值

}maps;

 

maps map[MAXV][MAXV];

int dis[MAXV]; //用于dinic分层

 

int vertime; //顶点总数

int nedges; //边的总数

int power_stations; //发电站总数

int consumers; //消费者总数

int maxflow; //最大流

int sp,fp; //标记源点与汇点

 

bool bfs(){

int v,i;

queue <int>q;

memset(dis,0,sizeof(dis));

 

q.push(sp);

dis[sp]=1;

while(!q.empty()){

v=q.front();q.pop();

for(i=1;i<=vertime;i++)

if(!dis[i] && map[v][i].capacity>map[v][i].flow){

q.push(i);

dis[i]=dis[v]+1;

}

if(v==fp) return 1;//仍然能找到增广路,返回1

}

return 0;//遍历完所有节点,仍然没有fp,说明找不到增广路了

}

 

int dfs(int cur,int cp){

if(cur==fp) return cp;//如果访问到汇点,则结束。必须要找到汇点,即找到一条完整的增广路后才进行增广路的权值减少。如果找不到,直接return cp-tmp;从而处理该层节点的上一层节点。

 

int tmp=cp,t;

for(int i=1;i<=vertime;i++)

if(dis[i]==dis[cur]+1 && tmp && map[cur][i].capacity>map[cur][i].flow){

t=dfs(i,min(map[cur][i].capacity-map[cur][i].flow,tmp));

//由于我们最终需要得到最短增广路上的最小残留量,因此我们在判断一个新访问边是否为最小的同时,必须考虑前面的边是否更小,这里的参数cp作用就是表示前面已经访问的边最小值。起始时,temp为无限大,被map[sp][i].capacity-map[sp][i].flow替换,以后DFS中,已经被访问边的最小值(即cp)要与当前边进行比较。

当所有的DFS结束后,才能获得最终的t值,从栈顶逐渐把各个边加上这个增广路的最小权值

 

注意if之后的括号一直到temp-=t,必须满足边的要求才可以减边

map[cur][i].flow+=t;

map[i][cur].flow-=t;

tmp-=t;}

//之所以比匈牙利速度快,就在于这里不是找到一条增广路就直接退出了,而是在当前边减去t之后,继续for循环,在当前边减去之后,继续下一个可能的增广路。为了防止前面路径可能不够减的情况,特意将temp-t表示前面所有路径最短的在减去t的情况下,同时保证temp>0(for循环的条件),寻找下一个完整的增广路。

 

//n个点,m个边,总复杂度为O(m*n^2),一次dfs的复杂度为O(m*n),dfs次数为n

对于dinic是不会出现匈牙利算法中出现的:边(ij)被消除以后,又被反向地出现在增广路上,即边(i,j)变成(j,i),因为ij是有层数关系的,必须由ij,而不是反过来。因此在dfs中,找到一个增广路并且减去最小权值边后,该边就不再出现(一次BFS里,节点的dis值是确定的、不会被再次修改的,因此不可能返回)。因此共有m个增广路,找一条增广路的时间为n,至于为什么,需要看平摊分析(我的理解是:第一次找增广路需要遍历所有可能性,以后的增广路只需要在第一个基础上搜索即可),看下面:

 

我们现在来证每次搜索一条增广路的复杂度平摊是O(n)
搜索有三种类别:1.前进,2.后退,3.无效枚举。
前进必然会搜到一个“新”的点,“后退”会删掉一个“肯定无用”的点,那么前进和后退的总时间复杂度为O(n),无效枚举是指枚举到了一个已经被标记“无用”的点,而我们发现,对一条边(i,j)来说,j最多只会被i无效枚举1次,为什么呢?因为如果ji枚举了两次,说明i已经被“后退”了一次,说明i已经被删掉了,所以不可能在回来!因此每条边最多只会无效枚举1次。因为在m次搜索中,无效枚举的总次数是m,可以忽略。

 

 

因为最多进行ndfs(因为对于从源点到与其直接相连的点(假设为j),在更新ij到终点的增广路后,j点已经不再与终点有通路,必须换一个点,最多n-1个点都与源点相连,并且可以连接到终点

 

所以在Dinic算法中找增广路的总复杂度为O(m*n^2) ,这也是Dinic算法的总复杂度。

 

 

return cp-tmp;//表示增广路上前面节点都必须减去的权值量。这里不能简单return t,因为temp不止要减一次t

}

 

void dinic(){

maxflow=0;

while(bfs()) maxflow+=dfs(sp,MAXINT);//maxint为无限大。

}

 

int main(){

int i;

int x,y,z;

char ch;

while(scanf("%d%d%d%d", &vertime, &power_stations,&consumers,&nedges)!= EOF){

//Init

memset(map,0,sizeof(map));

 

//Read Gragh

for(i=1;i<=nedges;i++){ //设置读图从1开始

cin>>ch>>x>>ch>>y>>ch>>z;

map[x+1][y+1].capacity=z;

}

 

//Build Gragh

//建立超级源点指向所有的发电站,流量为有限值

sp=vertime+1;fp=vertime+2;vertime+=2;

for (i=1; i<=power_stations; i++){

cin>>ch>>x>>ch>>y;

map[sp][x+1].capacity=y;

}

 

//建立超级汇点,使所有消费者指向它

for (i=1; i<=consumers; i++){

cin>>ch>>x>>ch>>y;

map[x+1][fp].capacity=y;

}

 

dinic();

printf("%d\n",maxflow);

}

return 0;

}

maxflow1459dinic