会场安排问题(真·正解)

问题题面:

7-11 会场安排问题 (20 分)

题目来源:王晓东《算法设计与分析》

假设要在足够多的会场里安排一批活动,并希望使用尽可能少的会场。设计一个有效的 贪心算法进行安排。(这个问题实际上是著名的图着色问题。若将每一个活动作为图的一个 顶点,不相容活动间用边相连。使相邻顶点着有不同颜色的最小着色数,相应于要找的最小 会场数。)

输入格式:

第一行有 1 个正整数k,表示有 k个待安排的活动。 接下来的 k行中,每行有 2个正整数,分别表示 k个待安排的活动开始时间和结束时间。时间 以 0 点开始的分钟计。

输出格式:

输出最少会场数。

输入样例:

5
1 23
12 28
25 35
27 80
36 50 

输出样例:

在这里给出相应的输出。例如:

3

 

Solution:

这题的题面说是贪心做,但是好像怎么贪心都是错的。

首先指出一个网上很多的一个错误解法:按照结束时间排序,然后开始放活动,这样贪心可以使每次放的活动最多。

这样做是错的,这样只能保证第一次放的活动是最多的,但是具备有后效性不能使全局最优。网上的很多解法都是这个,是错误的。

比如数据:

6
90 98---1
8 32----2
18 48---3
16 82---4
83 84---5
39 89---6

这组数据的答案是3,我们把活动按照1-n编号,那么分别用三个会场装(2, 6)、(4, 5)、(3, 1),就能完成任务,答案是3。

但是如果按照贪心排结束时间的做法,这里给出我的代码:

(避免看不懂我的习惯性代码,给出宏定义(就是把for循环宏定义了一下,把rep看成for循环就好了):)

#define rep(i, a, b) for(__typeof(b) i = a; i <= (b); i++)

(正式代码:)

struct act{
	int l, r;
	bool vis = 0;
	friend bool operator < (act a, act b){
		return a.r < b.r;
	}
}a[maxn];

int n;

int solve(){
	int res = n, ret = 0;
	while(1){
		rep(i, 1, n) cout << a[i].vis << ' ' ;cout << endl;
		bool f = 0;
		int last = -1;
		rep(i, 1, n){
			if(!a[i].vis && a[i].l > last){
				last = a[i].r;
				a[i].vis = 1;
				f = 1;
			}
		}
		if(!f) break;
		ret++;
	}
	return ret;
}

int main()
{
	scanf("%d", &n);
	rep(i, 1, n) scanf("%d %d", &a[i].l, &a[i].r);
	sort(a + 1, a + n + 1);
	rep(i, 1, n) cout << a[i].l << ' ' << a[i].r << endl;
	printf("%d\n", solve());
	return 0;
}

这就是按照结束时间排序然后贪心地去拿。

我们看一下输出的过程:

8 32
18 48
16 82
83 84
39 89
90 98
0 0 0 0 0 0
1 0 0 1 0 1
1 1 0 1 0 1
1 1 1 1 0 1
1 1 1 1 1 1
4

上面是排序后的数组,中间是vis标记,1表示已经安排好,0表示尚未安排好,最后是答案

显然输出的为4,比正确答案大了1。

通过分析过程我们可以发现,的确在第一次拿的时候我们拿了很多,3个,但是这也导致后面的活动都不得不单独开一个会场,导致答案为4,不是最优解。这也就是典型的局部最优不能带来全局最优

那么正解是什么呢?

我倒觉得这个应该是一个模拟题,按照开始时间排序,来了一个活动就把它做了,如果没有空的会场就ans++,有的话就下一个,过程中维护一下会场的状态(空 or 正在使用)。

思路转换成代码就有些小技巧了,直接维护显然是比较麻烦的,我们把开始时间和结束时间分别装在两个数组中,从小到大排序,循环遍历开始时间a[i],设置位置标记j, 如果a[i] < b[j],说明要新开一个会场,ans++;否则j++,表示已经有会场能空出来了,更新最晚的时间。

Tips:实际这里我们维护了这样一个东西:在开的所有会场中,里面正在进行的会议结束时间最早的时间,这个在代码中是由b[j]来表示的。

int a[maxn], b[maxn];

int main()
{
	int n; scanf("%d", &n);
	rep(i, 1, n) scanf("%d %d", a + i, b + i);
	sort(a + 1, a + n + 1);
	sort(b + 1, b + n + 1);
	int j = 1;
	int ans = 0;
	rep(i, 1, n) {
		if(a[i] < b[j]) ans++;
		else j++;
	}
	printf("%d\n", ans);
	return 0;
}

如果实在理解不了这个写法就自己写一个维护各个会场状态的嘛,队列似乎是一个不错的选择,只不过比较麻烦了。

over.

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值