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

             从大三准备考研开始,基本就废弃了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   500

struct 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值