【省选】算法总结——网络流与二分图

网络流与二分图

 

至于为什么把两类归在一起,原因很简单,他们极为相似!

 

先说说比较特殊的二分图,再说一般化的网络流

 

二分图最大匹配

比如这样一个问题,有N头牛和M堆草,告诉你某些牛喜欢某些草,当然那头牛吃掉某堆草后那堆草就没了,问你最多有几头牛能吃到自己喜欢的草

 

我们连一条A→①的边来表示A牛喜欢①草,那么我们可以发现,牛之间不会有连边,而草之间同样不会有连边

这样我们把所有牛放在左边归入集合X,把草放在右边归入集合Y,这样就构成了一个二分图。

 

匈牙利算法可以求出二分图最大匹配

它引入了类似网络流退流的思想,具体不再多说

//X集有n个点,Y集有m个点

//g[A][B]表示A->B有边

bool search(int x)

{

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

         if(g[x][i] && !h[i])

         {

                   h[i]=1;

                   if(pre[i]==-1 || search(pre[i]))

                   {

                            pre[i]=x;

                            return 1;

                   }

         }

         return 0;

}

 

int MaxMatch()

{

         int res=0;

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

         {

                   memset(h,0,sizeof(h));

                   if(search(i)) res++;

         }

         return res;

}

 

int main()

{

    建图;

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

}

 

 

 

最大流

 

前面已经说了最大匹配和最大流之间是可以互相转化的,如下图

 

 

 

 

 

求最大流的算法很多

 

我个人觉得效率高代码短的是sap算法(其实也只会这一种。。。)

 

 

其他的不再多说,附上sap标号法代码

int sap(int x,int flow)

{

         if(x==n) return flow;

         int res=0;

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

         if(g[x][i] && h[x]==h[i]+1)

         {

                   int t=sap(i,min(flow-res,g[x][i]));

                   g[x][i]-=t;g[i][x]+=t;

                   if((res+=t)==flow) return flow;//饱和

         }

         if(h[1]>=n) return res;

         if(--v[h[x]]==0) h[1]=n;

         ++v[++h[x]];

         return res;

}

 

 

 

最优匹配

顾名思义,如果我们给上面的问题再加一个描述,也就是没堆草吃下去后所获得的营养不同,问能获得的最大营养是多少

显然,这个肯定是最大匹配的所有方案中去选择一个最优方案

这就要用到KM算法

 

不过KM我不会。。。。

但是,前面说过,可以转化成费用流来做

 

费用流

接着上面的话题,建模相信大家应该已经有一点想法了,同样加一个源和一个汇,源和每个起点连一条费用为0流量为1的边,而每个重点和汇连一条费用为0流量为1的边

这样图就建好了,跑一次费用流

怎么写?spfa就可以了,我把它称作连续最短路算法

 

有一次YY同学问我问什么这是对的,这里顺便我也理一下思路

Sap算法是每次随便找一条路然后填满,不过按理来说这种贪心是不可行的,但是引入了退流这个牛逼的东西后,这就对了!现在我们不再是盲目的随便找一条边增广,而是找费用最小的增广,这样保证每次增广出来的路费用最小,就能保证最终费用最小,并且在退流这个牛逼东西的影响下,最终我们还可以找出正确的最大流!

好了,证明就到这里了

 

算法基本过程以及很明了了,找一条最短路(要记录下路径),然后在这条最短路上尽可能多地填充流量

看看下面的代码

bool spfa()

{

         memset(inq,0,sizeof(inq));

         memset(dist,0x3f,sizeof(dist));

         dist[S]=0; q.push(S);

         while(!q.empty())

         {

                   int x=q.front();

                   q.pop();inq[x]=false;

                   for(int i=S;i<=T;i++)

                            if(g[x][i]>0 && dist[i]>dist[x]+w[x][i])

                            {

                                     pre[i]=x;

                                     dist[i]=dist[x]+w[x][i];

                                     if(!inq[i])

                                     {

                                               inq[i]=true;

                                               q.push(i);

                                     }

                            }

         }

         return dist[T]!= inf;

//如不为极大值就返回true,即成功找到一条最短路

}

int MFMC()

{

         memset(pre,-1,sizeof(pre));

         int maxflow=0,mincost=0;

         while(spfa())

         {

                   int res=1;

                   for(int i=T;i!=S;i=pre[i])

                   {

                            g[pre[i]][i]-=res;

                            g[i][pre[i]]+=res;

                   }

                   maxflow+=res;

                   mincost+=res*dist[T];

         }

         return mincost;

}

在spfa中唯一改动的地方就是红色部分,因为我们需要判断是否还有流量

想想一下水管,整个水管的流量取决于最细的一截,所以要在最短路上找到流量最小的一条边,填满

至于怎么记录最短路,很简单,一个pre[]数组搞定

 

不过遗憾的是,由很多情况的费用流点数很多,如N=10000,M=50000,这样用矩阵就存不下了,所以我们要用到邻接表,代码如下

void addedge(int a,int b,int c,int d)

{

         edge[L]=(EG){a,b,c,+d,head[a]};

         head[a]=L++;

         edge[L]=(EG){b,a,0,-d,head[b]};

         head[b]=L++;

}

bool spfa()

{

         memset(h,0,sizeof(h));

         memset(dist,0x3f,sizeof(dist));

         dist[S]=0;q.push(S);h[S]=1;

         while(!q.empty())

         {

                   int x=q.front();

                   q.pop();h[x]=false;

                   for(int i=head[x];i!=-1;i=edge[i].next)

                            if(edge[i].flow>0 && dist[edge[i].to]>dist[x]+edge[i].cost)

                            {

                                     pre[edge[i].to]=i;

                dist[edge[i].to]=dist[x]+edge[i].cost;

                if(!h[edge[i].to])

                {

                    h[edge[i].to]=true;

                                               q.push(edge[i].to);

                                     }

                            }

         }

         return dist[T]!=inf;

}

int MFMC()

{

         int mincost=0,maxflow=0;

         while(spfa())

         {

                   int res=inf;

                   for(int i=T;i!=S;i=edge[pre[i]].from)

                   {

                            res=min(res,edge[pre[i]].flow);

                   }

                   for(int i=T;i!=S;i=edge[pre[i]].from)

                   {

                            edge[pre[i]].flow-=res;

                            edge[pre[i]^1].flow+=res;

                   }

                   maxflow+=res;

                   mincost+=res*dist[T];

         }

         return mincost;

}

注意上面标红的几个地方

1)        首先是addedge模块,我们在用矩阵的时候是用了g[a][b]和g[b][a],这里同理,要加上两条边

2)        在spfa中的pre记录下当前边在链表中的位置

3)        pre[i]^1表示找到如(1)所说中该边所对的另一条边(听懂没?)

 

然后就是题目变形了,就像最优匹配那一题,它要求我们求出最大值,所以我们就要转换成最大费用最大流了,怎么转换?简单,spfa哪里的”>”改成”<”,然后memset那里赋极大值改成赋极小值即可

 

 

 

我做过的网络流(二分图)题目

费用流/最优匹配

花店橱窗布置 wikioi1028 http://blog.csdn.net/jiangzh7/article/details/8706627

疯狂的方格取数 vijos 1653 http://blog.csdn.net/jiangzh7/article/details/8706937

丘比特的烦恼 Vijos 1169 http://blog.csdn.net/jiangzh7/article/details/8735843

最大流/最大匹配

ditch http://blog.csdn.net/jiangzh7/article/details/8545279

COURSES POJ1469 http://blog.csdn.net/jiangzh7/article/details/8644173

Chessboard POJ2446 http://blog.csdn.net/jiangzh7/article/details/8665765

Way Selection Vijos1212 http://blog.csdn.net/jiangzh7/article/details/8731492

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值