问题题面:
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.