小韦老师@NOIP 普及组-2008-排座椅

小韦老师@NOIP 普及组-2008-排座椅

题目:

描述

上课的时候总会有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情。不过,班主任小雪发现了一些有趣的现象,当同学们的座次确定下来之后,只有有限的 D 对同学上课时会交头接耳。

同学们在教室中坐成了 M 行 N 列,坐在第 i 行第 j 列的同学的位置是 (i, j),为了方便同学们进出,在教室中设置了 K 条横向的通道,L 条纵向的通道。于是,聪明的小雪想到了一个办法,或许可以减少上课时学生交头接耳的问题:她打算重新摆放桌椅,改变同学们桌椅间通道的位置,因为如果一条通道隔开了两个会交头接耳的同学,那么他们就不会交头接耳了。

请你帮忙给小雪编写一个程序,给出最好的通道划分方案。在该方案下,上课时交头接耳的学生的对数最少。

输入

输入的第一行,有 5 个用空格隔开的整数,分别是 M,N,K,L,D(2 <= N,M <= 1000,0 <= K < M,0 <= L < N,D <= 2000)。

接下来的 D 行,每行有 4 个用空格隔开的整数。第 i 行的 4 个整数 Xi,Yi,Pi,Qi,表示坐在位置 (Xi,Yi) 与 (Pi,Qi) 的两个同学会交头接耳(输入保证他们前后相邻或者左右相邻)。

输入数据保证最优方案的唯一性。

输出

输出共两行。

第一行包含 K 个整数,a1,a2……aK,表示第 a1 行和 a1+1 行之间、第 a2 行和 a2+1 行之间、…、第 aK 行和第 aK+1 行之间要开辟通道,其中 ai < ai+1,每两个整数之间用空格隔开(行尾没有空格)。

第二行包含 L 个整数,b1,b2……bL,表示第 b1 列和 b1+1 列之间、第 b2 列和 b2+1 列之间、…、第 bL 列和第 bL+1 列之间要开辟通道,其中 bi < bi+1,每两个整数之间用空格隔开(列尾没有空格)。

输入样例1

4 5 1 2 3
4 2 4 3
2 3 3 3
2 5 2 4

输出样例1

2
2 4

提示

xxx

上图中用符号 *、※、+ 标出了 3 对会交头接耳的学生的位置,图中 3 条粗线的位置表示通道,图示的通道划分方案是唯一的最佳方案。

题解:

思路:
整体思路:

这道题可以用贪心求解,这里贪的是分隔的人数,尽可能多是将可能会交头接耳的两个同学分隔开。
1.对于划分 k 条横向通道:
如果把每两行之间划分通道能分隔的人数求出来,将这些人数从多到少排序,取前 k 个通道即可。
例如:
r 数组表示划分通道能分隔的人数,r[1] 表示把第 1 行和第 2 行划分通道能分隔的人数,r[2] 表示把第 2 行和第 3行划分通道能分隔的人数, ……,r[m-1] 表示把第 m-1 行和第m 行划分通道能分隔的人数。将 r 数组降序排列,取前 k 个,则就是划分通道的位置了。
2.对于划分 l 条纵向通道:
如果把每两列之间划分通道能分隔的人数求出来,将这些人数从多到少排序,取前 l 个通道即可。
例如:
c 数组表示划分通道能分隔的人数,c[1] 表示把第 1 列和第 2 列划分通道能分隔的人数,c[2] 表示把第 2 列和第 3列划分通道能分隔的人数, ……,c[n-1] 表示把第 n-1 列和第n 列划分通道能分隔的人数。将 c 数组降序排列,取前 l 个,则就是划分通道的位置了。
但是上面的思路有什么问题呢?
我们的两个数组都是一样的,下标表示划分通道的位置,对应的数组的值表示分隔的人数,
题目要求我们输出的是位置,而数组排序之后,数组的下标和对应的值不能再像原来那样对应了。
例如:原来 r[1] 代表的是把第 1 行和第 2 行划分通道能分隔的人数;而排完序后,r[1] 表示的是通过划分通道能分隔出的最多的人数,至于在什么位置划分,已经不知道了。所以我们需要将划分通道的位置和人数做“绑定”,故用结构体数组来存储。

具体步骤:

1.定义结构体以及结构体数组:

	const int N = 1e3 + 10;
	struct node {
		// 位置,表示第 pos 行和第 pos+1 行划分通道,或者表示第 pos 列和 第 pos+1 列划分通道 
		int pos;  
		int num;  // 划分通道能隔开的人数 
	} r[N], c[N];  // r 表示行横向划分通道,c 表示纵向划分通道  

2.定义 m, n, k, l, d,并且输入:

	int m, n;  // 教室是 m 行 n 列的 
	int k, l;  // 需设置 K 条横向的通道,L 条纵向的通道
	int d;     // 现在有 d 对同学会交头接耳 
	cin >> m >> n >> k >> l >> d;

3.定义变量,用来接收输入的会交头接耳的两位同学的坐标:

	int x1, y1;  // 表示会交头接耳的第 1 位同学的位置 
	int x2, y2;  // 表示会交头接耳的第 2 位同学的位置 

4.将 d 对会交头接耳的同学的坐标输入,并且存储:

	for (int i = 0; i < d; i++) {   
		cin >> x1 >> y1 >> x2 >> y2;
		int x = min(x1, x2);  // 将两位同学的最大横坐标记下 
		int y = min(y1, y2);  // 将两位同学的最大纵坐标记下
		if (x1 == x2) {  // 若两位同学的横坐标相同,则需要加纵向的通道进行分隔
			// 将 y 作为结构体数组 c 的下标是为了能将第 y 条通道分隔的人加 1 
			c[y].pos = y;  // 将位置记下  
			c[y].num++;  // 对应位置分隔的人数加 1 
		} else {  // 若两位同学的纵坐标相同,则需要加横向的通道进行分隔
			// 将 x 作为结构体数组 r 的下标是为了能将第 x 条通道分隔的人加 1 
			r[x].pos = x;  // 将位置记下 
			r[x].num++;  // 对应位置分隔的人数加 1 
		}		
	}

5.排序:

	// 根据 r 的定义,下标是表示位置的,所以下标范围是 1~m-1 
	// 根据 c 的定义,下标是表示位置的,所以下标范围是 1~n-1 
	sort(r + 1, r + m, cmp1);  // 根据分隔人数从多到少排列   
	sort(c + 1, c + n, cmp1);  // 根据分隔人数从多到少排列
	sort(r + 1, r + k + 1, cmp2);  // 将前 k 个按照位置从小到大排列 
	sort(c + 1, c + l + 1, cmp2);  // 将前 l 个按照位置从小到大排列

6.输出前 k 个横向通道的位置

	for (int i = 1; i <= k; i++) {  
		cout << r[i].pos;
		if (i < k) cout << " ";  // 记得行末无空格 
	} 

7.输出前 l 个横向通道的位置

	for (int i = 1; i <= l; i++) { 
		cout << c[i].pos;
		if (i < l) cout << " ";  // 记得行末无空格
	}

8.cmp1 函数的实现:

	bool cmp1(node a, node b) {  // 按照分隔人数从多到少排列 
		return a.num > b.num;  // 可以理解成分隔人数多的放到前面 
	}

9.cmp2 函数的实现:

	bool cmp2(node a, node b) {  // 按照位置从小到大排列
		return a.pos < b.pos;  // 可以理解成位置小的放在前面 
	}
思考:

1°本题哪里体现了贪心的思想?请说明理由
2°本题的这种贪心求法为什么能求得最优解?请说明理由
3°输入数据时,若结构体数组的下标不是 y 和 x,而是改成以下,是否正确?请说明理由

	int cnt1 = 0;  // 作为结构体数组 c 下标 
	int cnt2 = 0;  // 作为结构体数组 r 下标 
	for (int i = 0; i < d; i++) {  // 将 d 对会交头接耳的同学的坐标输入,并且存储 
		cin >> x1 >> y1 >> x2 >> y2;
		int x = min(x1, x2);  // 将两位同学的最大横坐标记下 
		int y = min(y1, y2);  // 将两位同学的最大纵坐标记下
		if (x1 == x2) {  // 若两位同学的横坐标相同,则需要加纵向的通道进行分隔 
			c[cnt1].pos = y;  
			c[cnt1].num++;
			cnt1++;
		} else {  // 若两位同学的纵坐标相同,则需要加横向的通道进行分隔
			r[cnt2].pos = x;
			r[cnt2].num++;
			cnt2++;
		}		
	}  

4°通过这个题目,你有哪些收获?这些收获你能学以致用麽?

完整代码:
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 10;
struct node {
	// 位置,表示第 pos 行和第 pos+1 行划分通道,或者表示第 pos 列和 第 pos+1 列划分通道 
	int pos;  
	int num;  // 划分通道能隔开的人数 
} r[N], c[N];  // r 表示行横向划分通道,c 表示纵向划分通道 

bool cmp1(node a, node b) {  // 按照分隔人数从多到少排列 
	return a.num > b.num;  // 可以理解成分隔人数多的放到前面 
}

bool cmp2(node a, node b) {  // 按照位置从小到大排列
	return a.pos < b.pos;  // 可以理解成位置小的放在前面 
}

int main() {

	int m, n;  // 教室是 m 行 n 列的 
	int k, l;  // 需设置 K 条横向的通道,L 条纵向的通道
	int d;     // 现在有 d 对同学会交头接耳 
	cin >> m >> n >> k >> l >> d;
	int x1, y1;  // 表示会交头接耳的第 1 位同学的位置 
	int x2, y2;  // 表示会交头接耳的第 2 位同学的位置 
	for (int i = 0; i < d; i++) {  // 将 d 对会交头接耳的同学的坐标输入,并且存储 
		cin >> x1 >> y1 >> x2 >> y2;
		int x = min(x1, x2);  // 将两位同学的最大横坐标记下 
		int y = min(y1, y2);  // 将两位同学的最大纵坐标记下
		if (x1 == x2) {  // 若两位同学的横坐标相同,则需要加纵向的通道进行分隔
			// 将 y 作为结构体数组 c 的下标是为了能将第 y 条通道分隔的人加 1 
			c[y].pos = y;  // 将位置记下  
			c[y].num++;  // 对应位置分隔的人数加 1 
		} else {  // 若两位同学的纵坐标相同,则需要加横向的通道进行分隔
			// 将 x 作为结构体数组 r 的下标是为了能将第 x 条通道分隔的人加 1 
			r[x].pos = x;  // 将位置记下 
			r[x].num++;  // 对应位置分隔的人数加 1 
		}		
	}
	// 根据 r 的定义,下标是表示位置的,所以下标范围是 1~m-1 
	// 根据 c 的定义,下标是表示位置的,所以下标范围是 1~n-1 
	sort(r + 1, r + m, cmp1);  // 根据分隔人数从多到少排列   
	sort(c + 1, c + n, cmp1);  // 根据分隔人数从多到少排列
	sort(r + 1, r + k + 1, cmp2);  // 将前 k 个按照位置从小到大排列 
	sort(c + 1, c + l + 1, cmp2);  // 将前 l 个按照位置从小到大排列
	for (int i = 1; i <= k; i++) {  // 输出前 k 个横向通道的位置 
		cout << r[i].pos;
		if (i < k) cout << " ";  // 记得行末无空格 
	} 
	cout << endl;
	for (int i = 1; i <= l; i++) {  // 输出前 l 个横向通道的位置
		cout << c[i].pos;
		if (i < l) cout << " ";  // 记得行末无空格
	}
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值