牛客网暑期ACM多校训练营(第五场)E.room (贪心+二分图-KM)

题目链接

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

Nowcoder University has 4n students and n dormitories ( Four students per dormitory). Students numbered from 1 to 4n.

And in the first year, the i-th dormitory 's students are (x1[i],x2[i],x3[i],x4[i]), now in the second year, Students need to decide who to live with.

In the second year, you get n tables such as (y1,y2,y3,y4) denote these four students want to live together.

Now you need to decide which dormitory everyone lives in to minimize the number of students who change dormitory.

输入描述:

The first line has one integer n.

Then there are n lines, each line has four integers (x1,x2,x3,x4) denote these four students live together in the first year

Then there are n lines, each line has four integers (y1,y2,y3,y4) denote these four students want to live together in the second year

输出描述:

Output the least number of students need to change dormitory.

示例1

输入

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

输出

2

说明

Just swap 4 and 5

备注:

1<=n<=100

1<=x1,x2,x3,x4,y1,y2,y3,y4<=4n

It's guaranteed that no student will live in more than one dormitories.

题意:给出N个宿舍(4人间)的住宿学生编号(1~4*N),由于需要换宿舍,再给出换宿舍后的住宿学生编号情况,求学生宿舍发生改变的最小值?

题解:为了达到最少人换宿舍,那么肯定是能在原来宿舍留下的人越多越好,这里使用二分图(或网络流)我们对于每个初始宿舍到最终的每个宿舍都可以建一条边,权值为相同编号个数(即留下的最多人数),只需要跑一波KM就能得到该二分图的最大值,也就是最多有多少人不用换宿舍,那么最少换宿舍人数=总人数-KM结果.

代码如下:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<fstream>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int maxn = 110;
int link[maxn], lx[maxn], ly[maxn], slack[maxn];    //lx,ly分别为x点集y点集的个数
int visx[maxn], visy[maxn], mp[maxn][maxn];
int n;
int dfs(int x)
{
	visx[x] = 1;
	for (int y = 1; y <= n; y++)
	{
		if (visy[y]) continue;
		int t = lx[x] + ly[y] - mp[x][y];
		if (t == 0)
		{
			visy[y] = 1;
			if (link[y] == -1 || dfs(link[y]))
			{
				link[y] = x;
				return 1;
			}
		}
		else if (slack[y] > t)//不在相等子图中stack取最小的
			slack[y] = t;
	}
	return 0;
}
int km()
{
	int i, j;
	memset(link, -1, sizeof(link));
	memset(ly, 0, sizeof(ly));
	for (i = 1; i <= n; i++)  //lx初始化为与它关联变中最大的
		for (j = 1, lx[i] = -inf; j <= n; j++)
			if (mp[i][j] > lx[i])	lx[i] = mp[i][j];
	for (int x = 1; x <= n; x++)
	{
		memset(slack, inf, sizeof(slack));
		while (1)
		{
			memset(visx, 0, sizeof(visx));
			memset(visy, 0, sizeof(visy));
			if (dfs(x)) //若成功,则该点增广路完成,进入下一个点的增广
				break;
			//若失败。则需要改变一些点的标号,使得图中可行边的数量增加
			//方法:将所有的增广路中的x方的点的标号全部减去一个常数d
			//      所有在增广路中的y方的点标号全部加上一个常数d
			int d = inf;
			for (int i = 1; i <= n; i++)
				if (!visy[i] && d > slack[i])
					d = slack[i];
			for (i = 1; i <= n; i++)
				if (visx[i])
					lx[i] -= d;
			for (i = 1; i <= n; i++)//修改顶标后,要把所有不在交错树中的y顶点的slack值都减去d
				if (visy[i])
					ly[i] += d;
				else
					slack[i] -= d;
		}
	}
	//for (i = 1; i <= n; i++)
	//	cout << link[i] << " ";
	//cout << endl;
	int res = 0;
	for (i = 1; i <= n; i++)
		if (link[i] > -1)
			res += mp[link[i]][i];
	return res;
}
int x[maxn][4], y[maxn][4];
void input() {
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < 4; j++)
			scanf("%d", &x[i][j]);
	for (int i = 1; i <= n; i++)
		for (int j = 0; j < 4; j++)
			scanf("%d", &y[i][j]);
}
int cal(int s, int t) {
	int vis[4 * maxn] = { 0 };
	for (int i = 0; i < 4; i++)
		vis[x[s][i]]++;
	int cnt = 0;
	for (int i = 0; i < 4; i++) {
		vis[y[t][i]]--;
		if (vis[y[t][i]] == 0)
			cnt++;
	}
	return cnt;
}
int main()
{
	while (~scanf("%d", &n))
	{
		input();

		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				mp[i][j] = cal(i, j);

		printf("%d\n", 4 * n - km());
	}
	return 0;
}

写这题博文是为了把我用的KM模板粘到博客上~

猜你喜欢

转载自blog.csdn.net/weixin_41156591/article/details/81430178