编程之美2014年资格赛第三题——格格取数(已过大数据)

题目3 :格格取数

时间限制:2000ms

单点时限:1000ms

内存限制:256MB

描述

给你一个m x n (1 <=m, n <= 100)的矩阵A (0<=aij<=10000),要求在矩阵中选择一些数,要求每一行,每一列都至少选到了一个数,使得选出的数的和尽量的小。

 

输入

多组测试数据。首先是数据组数T

对于每组测试数据,第1行是两个正整数m, n,分别表示矩阵的行数和列数。

接下来的m行,每行n个整数,之间用一个空格分隔,表示矩阵A的元素。

 

输出

每组数据输出一行,表示选出的数的和的最小值。

 

数据范围

小数据:1 <= m, n<= 5

大数据:1 <= m, n<= 100

 

 

样例输入

2

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

样例输出

Case 1: 3

Case 2: 5

题目要求使得每一行、每一列都至少有一个数被取出,并使得取出的和最小。开始就觉得是每行、每列都去一个这样是最小的。网上流传很广的一组样例:

4  4

1  1  100

100  100 1

100  100 1

能够很轻松的否定掉这个思路。开始按照这个思路写的最小费用最大流,是因为最大流为max(n,m)。但是,显然不是这个样子的。真正的流量是不能够确定的。所以,直接用最小费用最大流就不行了。

那么,这里我们可以进行一些处理。让没有必要的流量直接从源点流向汇点。这样一来,总流量就一定是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 <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <cstring>
#define INF 99999999
#define MAXN 20000
using namespace std;
const int M = 100010;//边
const int N = 500;

struct Node//边,点f到点t,流量为c,费用为w
{
	int st, ed;
	int flow, cost;
	int next;
}edge[M];

int head[N], dis[N], q[N], pre[N], cnt;//cnt为已添加的边数,head为邻接表,dis为花费,pre为父亲节点
bool vis[M];
int n, m, s ,t;
int graph[110][110];

void init()
{
	memset(head, -1, sizeof(head));
	cnt = 0;
}

void add_edge(int f, int t, int d1, int d2, int w)
{//f到t的一条边,流量为d1,反向流量d2,花费w,反向边花费-w(可以反悔)
	edge[cnt].st = f;
	edge[cnt].ed = t;
	edge[cnt].flow = d1;
	edge[cnt].cost = w;
	edge[cnt].next = head[f];
	head[f] = cnt++;

	edge[cnt].st = t;
	edge[cnt].ed = f;
	edge[cnt].flow = d2;
	edge[cnt].cost = -w;
	edge[cnt].next = head[t];
	head[t] = cnt++;
}

bool spfa(int s, int t, int n)
{
	int i, tmp, l, r;
	memset(pre, -1, sizeof(pre));
	for(i = 0; i < n; ++i)
    {
        dis[i] = INF;
    }
	dis[s] = 0;
	q[0] = s;
	l = 0, r = 1;
	vis[s] = true;
	while(l != r)
    {
		tmp = q[l];
		l = (l + 1) % (n + 1);
		vis[tmp] = false;
		for(i = head[tmp]; i!=-1; i = edge[i].next)
		{
			if(edge[i].flow && dis[edge[i].ed] > dis[tmp] + edge[i].cost)
			{
				dis[edge[i].ed] = dis[tmp] + edge[i].cost;
				pre[edge[i].ed] = i;
				if(!vis[edge[i].ed])
				{
					vis[edge[i].ed] = true;
					q[r] = edge[i].ed;
					r = (r + 1) % (n + 1);
				}
			}
		}
	}
	if(pre[t] == -1)
	{
	    return false;
	}
	return true;
}

void MCMF(int s, int t, int n, int &flow, int &cost)
{//起点s,终点t,点数n,最大流flow,最小花费cost
	int tmp, arg;
	flow = cost = 0;
	while(spfa(s, t, n))
    {
		arg = INF;
		tmp = t;
		while(tmp != s)
		{
			arg = min(arg, edge[pre[tmp]].flow);
			tmp = edge[pre[tmp]].st;
		}
		tmp = t;
		while(tmp != s)
		{
			edge[pre[tmp]].flow -= arg;
			edge[pre[tmp] ^ 1].flow += arg;
			tmp = edge[pre[tmp]].st;
		}
		flow += arg;
		cost += arg * dis[t];
	}
}

int main()
{
	int x;
	scanf("%d",&x);
	for(int r = 1;r <= x;r++)
	{
		init();
		scanf("%d%d",&m,&n);
		for(int i = 1;i <= m;i++)
		{
			for(int j = 1;j <= n;j++)
			{
				scanf("%d",&graph[i][j]);
				add_edge(i,2*m+j,1,0,graph[i][j]);
				add_edge(i,2*m+n+j,1,0,graph[i][j]);
				add_edge(i+m,2*m+n+j,1,0,graph[i][j]);
				add_edge(i+m,2*m+j,1,0,graph[i][j]);
			}
		}
		for(int i = 1;i <= m;i++)
		{
			add_edge(0,i,1,0,-MAXN);
			add_edge(0,i+m,n,0,0);
		}
		t = 2 * m + 2 * n + 1;
		for(int i = 1;i <= n;i++)
		{
			add_edge(i+2*m,t,1,0,-MAXN);
			add_edge(2*m+n+i,t,m,0,0);
		}
		s = t + 1;
		add_edge(s,0,n*m,0,0);
		add_edge(0,t,n*m,0,0);
		int flow ,cost;
		MCMF(s,t,t+2,flow,cost);
		printf("Case %d: %d\n",r,cost+(m+n)*MAXN);
	}
	return 0;
}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值