poj3258 二分及一些思考

题意:牛要到河对岸,在与河岸垂直的一条线上,河中有N块石头,给定河岸宽度L,以及每一块石头离牛所在河岸的距离,

          现在去掉M块石头,要求去掉M块石头后,剩下的石头之间以及石头与河岸的最小距离的最大值。

用二分做,但是开始写了三个版本的二分,全都wa。

无赖看了别人的二分,还是不理解,为什么他们写的就能过。

反复思索后,终于明白了:关键在于题目求的是什么。

做题思想:二分所求的最小距离的最大值mid,记录可以去掉的石头块数cnt(注意:当相邻的石头的距离小于等于mid,就可以去掉),

                  若cnt > M,h = h - 1 (这里是比较难理解的,也是我纠结挺久的地方),此时,应当回过头来看一下题目求的是什么:

                                                      寻找一个长度mid,使得可以去掉M块石头,剩下的石头中,石头间的最小距离为mid

                                                      但是cnt记录的是:相邻距离小于等于mid的块数,所以存在这样的一种情况  ---- cnt记录的所有石头中,

                                                      有很多块石头间的距离是等于mid,而距离小于mid的石头的块数是小于等于M的,此时,若将h的值减一,

                                                      那么h的值就变成一个小于题目所求的答案了。

                                                      举个例子就懂了:假设有9块石头,首尾的数都表示河岸。

                   石头的编号                      1     2     3     4     5        6         7     8      9  

                   石头到河岸的距离     0    4     5     7     9    12      16       19   23     26     28

                   相邻的距离                  4    1      2     2     3      4        3        3      3       2

                                                    假设l = 1,h = 5, M = 4    则mid = 3, 此时去掉的石头的编号为:2,3,5,7,9  cnt = 5

                                                    按照程序,h = h - 1 = 4,此时mid = 2,去掉的石头的编号就为:2,4,9     cnt = 3(cnt<M了,

                                                    当然也有可能cnt == M,总之就是cnt > M不成立了)

                                                    也就是说,之后求得的cnt <= M恒成立, 即题目的答案不在  l 和 h 之间了(这点

                                                   之前是让我很费解的地方),更准确地说,答案就是执行这次h-1之前的h值。

                  若cnt <= M,则l = l + 1,讨论一下:若cnt < M,明显当前的mid小了,l = l + 1;若cnt == M,则去掉cnt块石头后,

                                                   剩下的石头的最小值必然是大于mid的,所以进行操作l = l + 1。

                                                   然后解决上面遇到的问题,因为h已经小于答案了,而答案就是h+1,之后继续二分cnt <= M恒成立,

                                                   l 不断自增,直到跳出循环,因为答案为h+1,我们返回 l 的值,那么while的判断条件就是 l <= h,

                                                   当 l 自增到 h + 1时就跳出循环,l == h + 1,正好就是答案。

 写了这么多,就是分析了一下二分算法执行的过程,因为以前用的二分while判断条件都是 l < h,看样子以后做二分得多注意了,

稍不注意就会有很多致命的小bug,一定要将对 l 和 h 的操作以及 while的判断条件结合起来考虑,要对二分算法进行灵活的变化,

没有一成不变的模版,具体问题具体分析。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int L, N, M;
int d[50005];

bool cmp(int a, int b) {
    return a < b;
}

int BSearch(int l, int h, int k) {
    int m, last, cnt;
    while (l <= h) {
        m = (l + h) >> 1;
        last = cnt = 0;
        for (int i = 1; i <= N + 1; i++) {
            if (m >= d[i] - d[last]) cnt++;
            else last = i;
        }
        if (cnt > k) h = m - 1;
        else l = m + 1;
    }
    return l;
}

int main()
{
    scanf ("%d%d%d", &L, &N, &M);
    d[0] = 0;
    d[N+1] = L;
    for (int i = 1; i <= N; i++)
        scanf ("%d", &d[i]);
    sort(d+1, d+1+N, cmp);
    printf ("%d\n", BSearch(0, L, M));

    return 0;
}

















你可能感兴趣的:(算法)