编程之美 2014 格格取数(最小费用最大流)

来源:互联网 发布:网络歌手大赛 编辑:程序博客网 时间:2024/06/11 03:53

             从大三准备考研开始,基本就废弃了ACM,然后上次同实验室的叫上我一起参加这个比赛,好为接下来的面试做准备。比赛的时候过了小数据就没去想其他I的解法,坐等各位大神出结题报告。苟然不出所料,今天年一早就把这三题的解法仔细看了一遍,觉得格格取数这个题目还是挺不错的,而且对于最小费用做大流这个算法的一直没有去了解过,正好拿这个题好好学习一下这个算法!

           

             看了一下求最小费用最大流的算法,其实感觉就是一个spfa算法。不过正好,这么久没有练习最短路径算法,趁机学习学习。贴上自己的模板(链接表表示图),以后再来看(第一次发现写博客就是做笔记,最重要的是给自己看!)

struct E{//边的结构体int to,next,fw,cost;};

void addedg(int from,int to,int fw,int cost){edg[nodes].to=to; edg[nodes].fw=fw;edg[nodes].cost=cost;edg[nodes].next=list[from]; list[from]=nodes++;edg[nodes].to=from; edg[nodes].fw=0;edg[nodes].cost=-cost;edg[nodes].next=list[to];  list[to]=nodes++;}

void spfa(){int i;int n=sink;queue<int> Q;for(i=0;i<=sink;i++){dis[i]=INF;vis[i] =0;}dis[src]=0; vis[src]=1;Q.push(src);while(!Q.empty()){int t=Q.front(); Q.pop();int e;for(e=list[t]; e!=-1; e=edg[e].next){int to=edg[e].to;if(edg[e].fw && dis[to]>dis[t]+edg[e].cost){dis[to] = dis[t]+edg[e].cost;per[to] = e;if(!vis[to]){vis[to]=1;Q.push(to);}}}vis[t]= 0;}                                                                                                                                          }


有了这个模板,最小费用最大流算法没问题了,所以重要的就是如何构图了!看了一些构图方法,但是不知道其原理,不过在这篇博客(http://blog.csdn.net/catalyst1314/article/details/23683553)中看到的解释才明白,orz!!解释如下:


让没有必要的流量直接从源点流向汇点。这样一来,总流量就一定是n*m了。而对于要通过行点和列点的流量是多少呢?经过分析可以发现。如果第i行和第j列都已经取出了一个数(不是a[i][j]时),那么a[i][j]必然是不会被取出的。

现在要解决的问题就是如何保证行点和列点至少流过一次,且不会存在两个已经流过的点在被同时流一次。将行点和列点进行拆分。共2*m个行点和2*n个列点。那么就是源点到m个行点的流量为1,费用为负无穷,n个列点到汇点的流量为1,费用为负无穷。剩下的m个行点的流量为n,费用为0n个列点的流量为m,费用为0.源点到汇点的流量为n*m,费用为0.同时,我们需要建立一个超级源点,保证总流量一定是n*m。即超级源点到源点的流量是n*m。这样就能保证得到的费用是最小值了,最后的结果就是得到的费用加上(n+m)*负无穷。

可以看出来,如果某个行点和某个列点已经被选择,那么如果存在一个流流过这两个点,那么代价必然是a[i][j],显然不如直接从源点流向汇点所以,就避免出现第i行和第j列都已经取出了一个数且不是a[i][j]时,取出了a[i][j]这种多余情况。同时,可以得到,负无穷只要能使最大边权+负无穷小于0即可。

#include <stdio.h>#include <algorithm>#include <queue>#include <string.h>using namespace std ;#define INF    0x3ffffff#define MAXE   50500#define MAXV   500struct E{int to,next,fw,cost;};E edg[MAXE];int dis[MAXV],vis[MAXV],list[MAXV],per[MAXV];int flow,ans,nodes;int src,sink;void addedg(int from,int to,int fw,int cost){edg[nodes].to=to; edg[nodes].fw=fw;edg[nodes].cost=cost;edg[nodes].next=list[from]; list[from]=nodes++;edg[nodes].to=from; edg[nodes].fw=0;edg[nodes].cost=-cost;edg[nodes].next=list[to];  list[to]=nodes++;}void BuiltMap(int m,int n){int i,j;addedg(src,1,n*m,0);addedg(sink-1,sink,n*m,0);for(i=1;i<=m;i++){addedg(1,i+1,1,-INF);addedg(1,i+m+1,n,0);}for(i=1;i<=n;i++){addedg(2*m+1+i,sink-1,1,-INF);addedg(2*m+n+1+i,sink-1,m,0);}for(i=1;i<=m;i++)for(j=1;j<=n;j++){int c; scanf("%d",&c);addedg(1+i,2*m+1+j,1,c);addedg(1+i,2*m+n+1+j,1,c);addedg(m+1+i,2*m+1+j,1,c);addedg(m+1+i,2*m+n+1+j,1,c);}addedg(1,sink-1,n*m,0);}void spfa(){int i;int n=sink;queue<int> Q;while(true){for(i=0;i<=sink;i++){dis[i]=INF;vis[i] =0;}dis[src]=0; vis[src]=1;Q.push(src);while(!Q.empty()){int t=Q.front(); Q.pop();int e;for(e=list[t]; e!=-1; e=edg[e].next){int to=edg[e].to;if(edg[e].fw && dis[to]>dis[t]+edg[e].cost){dis[to] = dis[t]+edg[e].cost;per[to] = e;if(!vis[to]){vis[to]=1;Q.push(to);}}}vis[t]= 0;}}if(dis[sink] == INF) break;int e,to;for(to=sink ; to!=src; to=edg[e^1].to  ){e= per[to];edg[e].fw -= 1;edg[e^1].fw +=1;ans += edg[e].cost;}flow+=1;}}int main(){freopen("data.in","r",stdin);int n,m,j,c;scanf("%d",&c);for(j=1;j<=c;j++){scanf("%d%d",&m,&n);ans= src  =nodes= 0;sink = (n+m)*2+3;memset(list,-1,sizeof(list));BuiltMap(m,n);spfa();ans= ans+(n+m)*INF;printf("Case %d: %d\n",j,ans);}return 0;}

测试数据(没有用大数据去测试了)

3
3 3
1 2 3
3 1 2
2 3 1
5 5
1 2 3 4 5
5 1 2 3 4
4 5 1 2 3
3 4 5 1 2
2 3 4 5 1
3 3 
1 1 1
1 100 110
1 100 100


感触:不懂算法真可怕,有时怀疑学算法有多大用,自己高不成低不就的,不过就是喜欢,趁读书的时候,在做点自己喜欢的事情未尝不可!




0 0
原创粉丝点击