1083这道题是:给定x轴上一堆区间,想把它们分成若干组,在同一组内的区间要求两两都不能有重叠,问最少有几组。
1328这道题是:给定x轴上一堆区间,想把它们分成若干组,在同一组内的区间要求两两都重叠(组内所有区间至少有一个公共覆盖点),问最少有几组。
看起来很像,且都可以用贪心做。
对于1083,先把所有奇数端点+1,使之变成偶数,当然顺带把(起点,终点)里较小的那个放前面,这样就把输入变成了一堆区间。
一开始,我想任选一个区间开始,把所有跟他兼容的区间划到他那组,并把它们从区间集里删掉。看起来好像挺对,问题是有多个区间跟本区间兼容而那多个区间之间不兼容时,就不知道怎么选了,选最大的兼容集合是不能解决问题的。
后来,经朋友提示,可以这么做,把所有区间按结束时间排序,结束早的排前面。开始只有一个组,把区间一个个往组里放,如果当前区间能跟某个组已有的区间兼容,就把他放到那个组里,如果不能把他放到任何一个组里,就新开一个组。唯一需要注意的就是,如果一个区间可以放到多个组,怎么选。选结束时间最晚的一组。如果把它放到结束时间早的组里,可能就导致后面本来能跟结束时间早的组兼容的区间放不进来了。
后来看讨论,发现还可以把所有区间按开始时间排序,开始早的排前面。同样的把区间一个个往组里放,如果当前区间能跟某个组兼容,就随便把他放到一个能兼容的组,如果所有组都不能兼容,就新开一个组。能把某区间随便放到一个能兼容的组是因为,后面来的区间开始时间都比他晚,所以后面任何一个本来能放进组里的区间,还是能放进某区间没放的组里。这比上一个方法实现上还省事。
讨论中还说,可以把直线分成200份,计算每份被占用了多少次,(一份)占用次数的最大值就是最终要求的组数。首先,容易知道至少需要这么多组。然后,通过贪心算法的过程,如果某区间跟所有组都不兼容,一定是所有组都占用了包含区间开始点的一小段。这说明绝不会有某点占用数还不到组数,就新开一组的情况。所以最多需要这么多组。这个方法的巧妙之处就在于,他给出了最少有几组,却没给出分组方案,所以比贪心算法更简单。
按结束时间排序的代码:
#include <cstdio>
#include <iostream>
#include <utility>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN = 1000;
int groups[MAXN];
int indexes[MAXN];
struct Range {
int left;
int right;
} ranges[MAXN];
struct mycomp {
bool operator() (int x1, int x2) {
return ranges[x1].right < ranges[x2].right;
}
} mycomp_object;
int main() {
int t, n;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
int start, end;
for(int i = 0; i < n; ++i) {
scanf("%d %d", &start, &end);
if (start % 2 != 0) start += 1;
if (end % 2 != 0) end += 1;
if (start > end) swap(start,end);
ranges[i].left = start;
ranges[i].right = end;
indexes[i] = i;
}
sort(indexes, indexes+n, mycomp_object);
int glen = 0;
for (int i = 0; i < n; ++i) {
Range cur = ranges[indexes[i]];
int max_right = 0;
int max_idx = -1;
for (int j = 0; j < glen; ++j) {
if (groups[j] < cur.left && groups[j] > max_right) {
max_right = groups[j];
max_idx = j;
}
}
if (max_idx < 0) groups[glen++] = cur.right;
else groups[max_idx] = cur.right;
}
printf("%d\n", 10*glen);
}
return 0;
}
对于一个结构体数组,对下标排序的方法是跟别人学来的,以后记着继续这么用。
1328这道题:对于每一个小岛的坐标,容易求出以这个点为圆心,d为半径的圆与x轴的两个交点,如果雷达设在两点之间,就能覆盖这个岛。如果没交点,则无解,返回-1. 只要两个区间有交集,就可以把雷达架在交集里,同时覆盖两个岛。所以希望在同一处相交的区间越多越好。
把这些区间按起始点排序后,如果第一个区间跟第二个区间没交集,那么一定需要单独用一个雷达去覆盖第一个区间。如果有交集,那可以再往下看,看这两个区间的交集的最右端,也就是结束较早的区间的右端,是不是跟第三个区间相交。如果没交集,那一定需要单独用一个雷达去覆盖第一和第二个区间,如果有交集,就可以再往下看,直到相交的部分与下一个区间没交集为止,这时分配一个雷达给前几个区间。
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 1000;
int indexes[MAXN];
struct Range {
float left;
float right;
} ranges[MAXN];
struct mycomp{
bool operator() (int x, int y) {
return ranges[x].left < ranges[y].left;
}
} mycompobject;
int main() {
int t = 0, n, d;
while (scanf("%d %d", &n, &d) == 2) {
if (!n && !d) break;
++t;
bool solve = true;
for (int i = 0; i < n; ++i) {
int tmp_x, tmp_y;
scanf("%d %d", &tmp_x, &tmp_y);
if (tmp_y > d || !solve) {
solve = false;
continue;
}
float sq = sqrt((d+tmp_y)*(d-tmp_y));
ranges[i].left = tmp_x-sq;
ranges[i].right = tmp_x+sq;
indexes[i] = i;
}
if (!solve) {
printf("Case %d: -1\n", t);
continue;
}
sort(indexes, indexes+n, mycompobject);
int cnt = 1;
float cur_right_most = ranges[indexes[0]].right;
for (int i = 1; i < n; ++i) {
if (cur_right_most < ranges[indexes[i]].left) {
++cnt;
cur_right_most = ranges[indexes[i]].right;
}
else {
cur_right_most = min(cur_right_most, ranges[indexes[i]].right);
}
}
printf("Case %d: %d\n", t, cnt);
}
return 0;
}