题意
给定若干个点的坐标,与范围R。每个点可以选择是否标记,标记后这个点的左右范围R内的所有点也会被标记,求为使所有点被标记,我们要主动标记多少个点。
思路
这题总感觉可以用搜索做,毕竟每个点的状态要么是1要么是0。但这是书上贪心的例题,那肯定得用贪心做啦。
贪心的原则是什么呢?
错解
首先我们的目标是尽可能少的标记,因此我们当然可以希望每次一次性标记的点越多越好,那么我们是不是可以遍历每个点,每次选择一次性标记数最多的点来标记呢?显然不行 我们可以看一下这个数据
1 9
1 2 2 3 3 3 4 4 5
显然在这种规则下程序会选择标记3,一口气标记了7个数字呢!然后呢,望不到彼此的1和5只能自己标记自己,因此最后我们主动标记了3个数。显然不是最优解。(其实这就是我的原始想法)
正解
我们可以从最左边开始考虑。在上述例子的条件下,我们肯定不会标记“1”,因为这样会浪费左边的范围,即标记“2”是优于标记“1”的。但是标记“3”就不是最优解了,为什么呢?因为标记“3”覆盖不到“1”,也就是留了个孤零零的元素没标记,这样子我们不得不单独的标记"1"。总而言之,标记“1”会浪费左边的空间,所以我们会试着选择标记“2”以及后面的元素,但是标记“3”不能照顾到”1“,带来了额外的成本,因此我们会选择标记2。所以我们希望被标记的点尽量靠右,不浪费左边的空间,但是也不能不覆盖最左边未标记的元素(即留空子)。
因此,贪心的原则应该是这样。
每次给最左边未标记的点的R范围内,最靠右的点标记。然后不断重复这一过程。
代码实现
书上的代码
int N, R;
int X[MAXN];
void solve(){
sort(X, X + N);
int i = 0, ans = 0;
while(i < N){
int s = X[i++];//s是没有被覆盖的最左侧的位置
while(i < N && X[i] <= s + R) i++;
//一直向右前进,直到到达距离s的距离严格大于R的点(此处用的是小于等于号)
//相当于拓展p点的左侧
int p = X[i - 1];//p是新加上标记的点的位置,因为X[i]是接触不到s的
while (i < N && X[i] <= p + R) i++;
//一直向右前进,直到到达距离p的距离严格大于R的点
//相当于拓展p点的右侧
ans++;
}
printf("%d\n", ans);
}
我的代码
int r, n;
int troop[2000];//存储每只队伍的坐标
sort(troop, troop + n);
int tag = 0, ans = 0;tag是没有被覆盖的最左侧的位置
troop[n] = 0x3f3f3f3f;//哨兵元素
for (int i = 0; i <= n; i++)
{
if (troop[i] - r > troop[tag]) {
ans++;
//标记点+1,没有明说,但标记的是i - 1
while (troop[i - 1] + r >= troop[tag]) tag++;
//更新标记点的右侧,也就是更新标记点的位置
}
//printf("%d---%d---%d\n", troop[i], troop[tag], ans);
}
printf("%d\n", ans);
比较一下
首先二者的思路是一致的。感觉区别在于书上的代码是一股气从左冲到右,而我的是一个一个检查,延迟标记(这也是为什么有一个哨兵的原因)
问题
我的问题主要在于一开始没有设置一个哨兵元素,然后我用很多if语句去判断各种情况来修补bug,但是修修补补终究不能AC。后来意识到标记不是即时的,才设置了一个哨兵元素,结果之前的if语句全部可以删掉,完美AC。可能一开始的方向就很重要吧。
参考书籍:《挑战程序设计竞赛》