2018ACM上海大都会赛: I. Matrix Game(最小费用最大流)

题目描述

At the start of the matrix game, we have an N x M matrix. Each grid has some balls.
The grid in (i,j) (0 ≤ i < N, 0 ≤ j < M) has Aij balls.
In each operation you can remove one ball from a grid or add one ball into a grid.
The goal of this game is to make each of the rows has the same number of balls and each of the columns has the same number of balls.
What is the minumun operations you should use?

输入描述:

The first line of the input is T(1≤ T ≤ 100), which stands for the number of test cases you need to solve.
The first line of each test case contains two integers N and M (1 ≤ N,M ≤ 20).
The next N lines describe Aij, each line contains M integers. (0 ≤ Aij ≤ 20).

输出描述:

For each test case, print the case number and the answer.

输入

2
2 3
4 8 5
2 4 6
3 3
1 5 2
3 5 4
2 3 4

输出

Case 1: 7
Case 2: 7

题意:给你n*m的矩阵,矩阵中每个格子上都有若干个石子(可能没有),每次可以从任意一格中拿走一颗石子,也可以往上放一颗石子,求最少的操作数使得每行的石子和都一样,每列的石子和都一样(所有数据<=20)

考虑暴力每行石子和,每列石子和

假设每行石子和为X,每列石子和为Y,一个很显然的公式就是X*n=Y*m,所以当X确定了,Y也就确定了

之后对于每一组(X, Y)用最小费用最大流求解

建图方式:

  1. 源点向所有行建边,流量为X,费用为0;所有列向汇点建边,流量为Y,费用为0
  2. 每一行向每一列都建边,流量为inf,费用为1
  3. 如果第i行第j列的格子上有k个石子,那么第i行向第j列建边,流量为k,费用为-1

之后求出最大流,那么最少的操作数就是(石子总数-最小费用)

直接暴力复杂度O(n*P),其中P为最小费用最大流模板复杂度,超时!

优化:

  • 可以发现每次建的图都一样,唯一区别就是①中的边流量大小不一样
  • 这样就没必要重新建图,每次只需要动态增加某些边的流量即可
  • 复杂度约为O(P)
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
#include<vector>
using namespace std;
#define inf 1044266558
vector<int> G1, G2;
typedef struct R
{
	int from, to;
	int c, w, op, next;
}Edge;
Edge G[11500];
int vis[88], dis[88], last[88], a[24][24], S, T, cnt, ans;
void Add(int u, int v, int c, int w) 
{
	G[++cnt].from = u, G[cnt].to = v;
	G[cnt].c = c, G[cnt].w = w;
	G[cnt].op = cnt+1, G[cnt].next = last[u];
	last[u] = cnt;
	G[++cnt].from = v, G[cnt].to = u;
	G[cnt].c = 0, G[cnt].w =- w;
	G[cnt].op = cnt-1, G[cnt].next = last[v];
	last[v] = cnt;
}
int dfs(int x, int now)
{
	int i, ret, flow;
	if(x==T || now==0)
		return now;
	ret = 0, vis[x] = 1;
	for(i=last[x];i!=0;i=G[i].next)
	{
		if(G[i].c && dis[G[i].to]+G[i].w==dis[x] && vis[G[i].to]==0)
		{
			flow = dfs(G[i].to, min(G[i].c, now-ret));
			ans += flow*G[i].w;
			G[i].c -= flow;
			G[G[i].op].c += flow;
			ret += flow;
			if(ret==now)
				break;
		}
	}
	return ret;
}
bool Sech()
{
	int i, now, j;
	now = inf;
	for(i=0;i<=T;i++)
	{
		if(vis[i]==0)
			continue;
		for(j=last[i];j!=0;j=G[j].next)
		{
			if(vis[G[j].to]==0 && G[j].c)
				now = min(now, -dis[i]+G[j].w+dis[G[j].to]);
		}
	}
	if(now==inf)
		return 0;
	for(i=0;i<=T;i++)
	{
		if(vis[i])
			dis[i] += now;
	}
	return 1;
}
int Gcd(int x, int y)
{
	if(y==0)
		return x;
	return Gcd(y, x%y);
}
int main(void)
{
	int Tc, n, m, i, j, sum, lcm, cas = 1;
	scanf("%d", &Tc);
	while(Tc--)
	{
		sum = 0;
		G1.clear();
		G2.clear();
		scanf("%d%d", &n, &m);
		cnt = ans = 0;
		S = 0, T = n+m+1, lcm = n*m/Gcd(n, m);
		memset(last, 0, sizeof(last));
		memset(dis, 0, sizeof(dis));
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=m;j++)
			{
				scanf("%d", &a[i][j]);
				sum += a[i][j];
			}
		}
		for(i=1;i<=n;i++)
		{
			G1.push_back(cnt+1);
			Add(S, i, -lcm/n, 0);
		}
		for(i=1;i<=m;i++)
		{
			G2.push_back(cnt+1);
			Add(n+i, T, -lcm/m, 0);
		}
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=m;j++)
			{
				if(a[i][j])
					Add(i, j+n, a[i][j], -1);
				Add(i, j+n, inf, 1);
			}
		}
		int res = inf;
		for(j=0;j<=20*n*m;j+=lcm)
		{
			for(i=0;i<G1.size();i++)
				G[G1[i]].c += lcm/n;
			for(i=0;i<G2.size();i++)
				G[G2[i]].c += lcm/m;
			while(1)
			{
				memset(vis, 0, sizeof(vis));
				while(dfs(S, inf))
					memset(vis, 0, sizeof(vis));
				if(Sech()==0)
					break;
			}
			res = min(res, ans+sum);
		}
		printf("Case %d: %d\n", cas++, res);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/81437612