题目大意:
给几个发电站,给几个消耗站,再给几个转发点。
发电站只发电,消耗站只消耗电,转发点只是转发电,再给各个传送线的传电能力。
问你消耗站能获得的最多电是多少
思路:建立一个超级源点和超级汇点,把发电站相连起来,消耗站连起来 然后就是模版的力量了
在此也讲了下dinic的原理:
求最大流的本质,就是不停的寻找增广路径。直到找不到增广路径为止。
对于这个一般性的过程,Dinic算法的优化如下:
(1)
Dinic算法首先对图进行一次BFS,然后在BFS生成的层次图中进行多次DFS。
层次图的意思就是,只有在BFS树中深度相差1的节点才是连接的。
这就切断了原有的图中的许多不必要的连接。很牛逼!
这是需要证明的,估计证明也很复杂。
(2)
除此之外,每次DFS完后,会找到路径中容量最小的一条边。
在这条边之前的路径的容量是大于等于这条边的容量的。
那么从这条边之前的点,可能引发出别的增广路径。
比如说 S -> b -> c -> d -> T 是一条增广路径,容量最小的边是 b -> c。
可能存在一条 S -> b -> e -> f -> g -> T 这样的增广路径。
这样的话,在找到第一条增广路径后,只需要回溯到 b 点,就可以继续找下去了。
这样做的好处是,避免了找到一条路径就从头开始寻找另外一条的开销。
也就是再次从 S 寻找到 b 的开销。
这个过程看似复杂,但是代码实现起来很优雅,因为它的本质就是回溯!
(3)
在同一次 DFS 中。如果从一个点引发不出任何的增广路径,就将这个点在层次图中抹去。
#include <stdio.h>
#include <string.h>
#define VM 150
#define EM 20550
#define inf 0x3f3f3f3f
struct edge
{
int frm,to,cap,next;
}e[EM];
int head[VM],dep[VM],ep,n;
//dep为点的层次
void addedge (int cu,int cv,int cw) //第一条边下标必须为偶数
{
e[ep].frm = cu;
e[ep].to = cv;
e[ep].cap = cw;
e[ep].next = head[cu];
head[cu] = ep;
ep ++;
e[ep].frm = cv;
e[ep].to = cu;
e[ep].cap = 0;
e[ep].next = head[cv];
head[cv] = ep;
ep ++;
}
int BFS (int src,int des)
//求出层次图
{
int que[VM],i,front = 0,rear = 0;
memset (dep,-1,sizeof(dep));
que[rear++] = src;
dep[src] = 0;
while (front != rear)
{
int u = que[front++];
front = front%VM;
for (i = head[u];i != -1;i = e[i].next)
{
int v = e[i].to;
if (e[i].cap > 0&&dep[v] == -1) //容量大于0&&未在dep中
{
dep[v] = dep[u] + 1;
//建立层次图
que[rear ++] = v;
rear = rear % VM;
if (v == des)
//找到汇点 返回
return 1;
}
}
}
return 0;
}
int dinic (int src,int des)
{
int i,res = 0,top;
int stack[VM];
//stack为栈,存储当前增广路
int cur[VM];
//存储当前点的后继 跟head是一样的
while (BFS(src,des))
//if BFS找到增广路
{
memcpy (cur,head,sizeof (head));
int u = src;
//u为当前结点
top = 0;
while (1)
{
if (u == des)
//增广路已全部进栈
{
int min = inf,loc ;
for (i = 0;i < top;i ++)
//找最小的增广跟并loc记录其在stack中位置
if (min > e[stack[i]].cap)
//以便退回该边继续DFS
{
min = e[stack[i]].cap;
loc = i;
}
for (i = 0;i < top;i ++)
//偶数^1 相当加1 奇数^1相当减1 当正向边 = 0&&路径不合适时,正加负减
{
//偶数是正向边,奇数是负向边,边从0开始
e[stack[i]].cap -= min;
e[stack[i]^1].cap += min;
}
//将增广路中的所有边修改
res += min;
top = loc;
u = e[stack[top]].frm;
//当前结点修改为最小边的起点
}
for (i = cur[u];i != -1;cur[u] = i = e[i].next)
//找到当前结点对应的下一条边
if (e[i].cap != 0&&dep[u] + 1 == dep[e[i].to])//不满足条件时,修改cur值(去掉不合适的占)eg:1-->2 1-->3 1-->4 有边 但只有
break;
// 1-->4 这条边满足条件 就把1到2、3的边给去掉
if (cur[u] != -1)
//当前结点的下一条边存在
{
stack[top ++] = cur[u];
//把该边放入栈中
u = e[cur[u]].to;
//再从下个点开始找
}
else
{
if (top == 0)
//当前结点无未遍历的下一条边且栈空,DFS找不到下一条增广路
break;
dep[u] = -1;
//当前结点不在增广路中,剔除该点
u = e[stack[--top]].frm; //退栈 回朔,继续查找
}
}
}
return res;
}
int main ()
{
int np,nc,m,v1,v2,w;
int src,des;
char str[20];
while (scanf ("%d%d%d%d",&n,&np,&nc,&m)!=EOF)
{
ep = 0;
src = n;
des = n+1;
memset (head,-1,sizeof(head));
while (m --)
{
scanf ("%s",str);
sscanf (str,"(%d,%d)%d",&v1,&v2,&w);
addedge (v1,v2,w);
}
while (np --)
{
scanf ("%s",str);
sscanf (str,"(%d)%d",&v2,&w);
addedge (src,v2,w);
}
while (nc--)
{
scanf ("%s",str);
sscanf (str,"(%d)%d",&v1,&w);
addedge (v1,des,w);
}
int ans = dinic (src,des);
printf ("%d\n",ans);
}
return 0;
}
思路:建立一个超级源点和超级汇点,把发电站相连起来,消耗站连起来 然后就是模版的力量了
在此也讲了下dinic的原理:
求最大流的本质,就是不停的寻找增广路径。直到找不到增广路径为止。
对于这个一般性的过程,Dinic算法的优化如下:
(1)
Dinic算法首先对图进行一次BFS,然后在BFS生成的层次图中进行多次DFS。
层次图的意思就是,只有在BFS树中深度相差1的节点才是连接的。
这就切断了原有的图中的许多不必要的连接。很牛逼!
这是需要证明的,估计证明也很复杂。
(2)
除此之外,每次DFS完后,会找到路径中容量最小的一条边。
在这条边之前的路径的容量是大于等于这条边的容量的。
那么从这条边之前的点,可能引发出别的增广路径。
比如说
可能存在一条 S -> b -> e -> f -> g -> T 这样的增广路径。
这样的话,在找到第一条增广路径后,只需要回溯到 b 点,就可以继续找下去了。
这样做的好处是,避免了找到一条路径就从头开始寻找另外一条的开销。
也就是再次从 S 寻找到 b 的开销。
这个过程看似复杂,但是代码实现起来很优雅,因为它的本质就是回溯!
(3)
在同一次 DFS 中。如果从一个点引发不出任何的增广路径,就将这个点在层次图中抹去。
#include <stdio.h>
#include <string.h>
#define VM 150
#define EM 20550
#define inf 0x3f3f3f3f
struct edge
{
}e[EM];
int head[VM],dep[VM],ep,n;
void addedge (int cu,int cv,int cw)
{
}
int BFS (int src,int des)
{
}
int dinic (int src,int des)
{
}
int main ()
{
}