我的dinic算法网络流(详注解)

本文详细解读了最大网络流算法的核心概念及其在解决实际问题中的应用,包括如何求解整个图的最大流和特定路径上的最大流量。通过实例演示算法步骤,并提供了优化技巧和注意事项,旨在帮助读者深入理解并灵活运用这一经典算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*题目大意:求一个图中起点s到终点t的最大流除以s到t的的所有路径中的最大流量的那条路径流量值;
(虽然这样看起来比较简单,但说实话,这题我读题至少都花了半个小时,直到ac的的前一秒我都有点怕题意读错)
 
   此题主要要求两个量:整个图的最大流和一路径的最大流量值;
 
   
        最大流maxflow没什么好说的,直接套模板(不过此题完全照搬是不行的,需要做一点修改),
     虽然我本打算自己去实现的练练手的,但是当我正打算手动实现的时候,阮学长建议我大比赛的
     时侯有模板尽量用模板,所以我就去拿到了那本模板书, 照着敲,发现这模板却是写得好,要让我敲得话,
     肯定至少得花一个小时左右!完全套模板的不能ac的原因是: 这题的head[i]存放的是-1,而模板却是把它作为0来处理的,
     所以的对模板做了一下修改! 另外在敲得过程中发现他的cur[MAXN]纯粹属于浪费空间,果断删除!
     

 一路径的最大流量值maxedge,之前阮学长打算以求最大路径的方法来求这个值,当我看到他用到spafa算法的时候,
 我就知道他这里可能理解错了,所以和他讨论,他也将他的代码删掉了,后来讨论用深搜的方法求,
 但是感觉 有环,处理起来很麻烦,阮和王相去讨论另一道题了,之后我又想了想,
 为什么不在求最大流的时候,注入我的maxedge,后来果断打擂方式,一较短的代码一次性ac!  
 虽然最大网络流都已经是很经典的模板了,感觉没多大必要讲的,但是有考虑到如果只会套模板,
 不知里面的工作原理,在正式比赛中很难随需求任意修改模板里面的处理细节!



 所以我还是在下面大概说一下最大流的一种比较经典的,也比较实用的一个网络流算法吧
 
   
     第一步:以原点s为根节点,将所有的节点按深度分层,(这个模板有个比较beautiful的地方就是利用路径数组ps来做队列
            ,节省了很大的空间)直到将汇点t找到为止,如果直到最后都没找到t那 说明没有路径了!最大流搞定!

     第二部:从s开始,一 层一层的向下寻找t,找到之后,求出最小容量边tr,以此为流量将各边减去tr,在将各边的反向边加tr!
                跳到第三步;
       
     第三部: 回溯到f点(f点是满足离s最近且,以该点为端点的边容量被减为了0);之后跳到第二步,继续寻找到t的路径!当找不到t的时候,
                又跳到第一步;

注解提供者:GHQ(SpringWater)
  */
#include<stdio.h>
#include<string.h>
#define MAXN 20
#define MAXE 40
#define typec int
const typec inf = 0x3f3f3f3f;
struct edge{int x,y,nxt; typec c;}bf[MAXE];//bf用来查找某节点相连的所有节点
int ne,head[MAXN],ps[MAXN],dep[MAXN];
void addedge(int x,int y,typec c)
{
    bf[ne].x=x;bf[ne].y=y;bf[ne].c=c;
    bf[ne].nxt=head[x];head[x]=ne++;
    bf[ne].x=y;bf[ne].y=x;bf[ne].c=0;
    bf[ne].nxt=head[y];head[y]=ne++;
}
typec flow(int n,int s,int t)
{
    typec tr,res=0;
    int i,j,k,l,r,top;
    while(1){
        memset(dep,-1,n*sizeof(int));
        for(l=dep[ps[0]=s]=0,r=1;l!=r;)//该部分为分层,dep[i]表示节点i在第几层,
                                        //而用到ps[l,r]在 这里做广搜 的队列l表示队列左边 ,r表示队列的右边;
        {
            for(i=ps[l++],j=head[i];j!=-1;j=bf[j].nxt)//i为从队列头取一个元素赋值给i
            {
                if(bf[j].c&&-1==dep[k=bf[j].y]){//当找到一个点相邻点,却流量不为0,同时还没有被分层!
                    dep[k]=dep[i]+1;ps[r++]=k;
                    if(k==t)//当发现汇点已经被分层,则结束分层,
                    {
                        l=r;
                        break;
                    }
                }
            }
        }
        if(dep[t]==-1)break;//当发现不能分层,说明已经找不到汇点,最大网络流结束!

        for(i=s,top=0;;)
        {
            if(i==t)//当一层层的深搜,找到了t,则需找出s到t路径中的最小容量tr,正向边减去该tr,反向边加上tr
            {
                for(k=0,tr=inf;k<top;++k)//路径边的编号保存在 ps[top]里,
                    if(bf[ps[k]].c<tr)tr=bf[ps[l=k]].c; //打雷获得最小tr,注意这里的 l=k,l将记录路径ps【l】
                                                        //可以起到找到里t最近的那个正向边减去tr为0的点bf[ps[l]].x,方便后面回溯!
                    for(k=0;k<top;++k)//正向边减tr,反向变加tr
                        bf[ps[k]].c-=tr,bf[ps[k]^1].c+=tr;
                    res+=tr;i=bf[ps[top=l]].x;//最大流res+tr,并将回溯到的目标点,赋值 给当前点i
            }
            for(j=head[i];j!=-1;j=bf[j].nxt)//从当前点i找一个可以到达的点bf[j].y
                if(bf[j].c&&dep[i]+1==dep[bf[j].y])break;
            if(j!=-1)//当找到一个可以到达的点,则可以跳到下一层,把当前点i改变为下一层的点bf[j].y,并将j放到为路径ps堆栈里
            {
                ps[top++]=j;//将j放到为路径ps堆栈里
                i=bf[j].y;//当前点i改变为下一层的点bf[j].y
            }
            else//当当前 i点,不能跳转到下一层,则需要回溯
            {
                if(!top)break;//当路径堆栈top==0时,说明i==s点,无法回溯!
                dep[i]=-1;i=bf[ps[--top]].x;//否则存在可回溯点,因为当前点i在该种分层法则不行的,
                                            //可将dep[i]除掉(dep[i]=-1),再将回溯的bf[ps[--top]].x点复制给i!
            }
        }
    }
    return res;
}
int main()
{
    int T,cas,e,s,t,n,maxflow,i;
    int x,y,c;
    double ans;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d%d",&cas,&n,&e,&s,&t);
        memset(head,-1,sizeof(head));
        for(i=ne=0;i<e;i++)
        {
            scanf("%d%d%d",&x,&y,&c);
            addedge(x,y,c);
        }
        maxedge=0;
        maxflow=flow(n,s,t);
        
        ans=(double)maxflow/(double)maxedge;
        printf("%d %0.3lf\n",cas,ans);
        
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值