P2678 跳石头

答案具有单调性,即存在一个最优解(最短条约距离的最大值),使得大于此解值的值都需要移走多于M个石头,而且所有小于此解值的所有值都是可行解(但他们不是最优的,所以不是答案). 

可以先假定一个值,检测他是否为可行解,并且通过多次这样的假定得出最优解.

使用函数bool C(x),若解x可行则返回true,否则返回false.

根据答案的单调性,接下来通过二分来假定答案就可以求解.二分的写法:

    int l = 0, r = 1000000000;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (C(mid))
            l = mid;
        else
            r = mid - 1;
    }

此时l(l == r)即为最优解.

注意到,这里 l = mid; r = mid - 1; 是根据实际问题选择的,在这里含义是如果mid值可行,那么在mid及其右侧可能有比mid更优的解(当然,首先得有可行解,此时可行即比mid更优).

如果mid值不可行,显然mid及其右侧均无可行解,不需要考虑这一区间了.

出于上述考虑,会写出:

if (C(mid))
    l = mid;
else
    r = mid - 1;

而一旦你的问题采用了这种形式,就必须有:

int mid = l + r + 1 >> 1;

才能保证不会陷入死循环.

AC代码:

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

int L, N, M, s[50010];

// 思路: s[0]为0, 即起点, s[N + 1]为L, 即终点
// 对于s[i], 检查其前方有多少个石头是在条约距离x以内(不含)的, 每有一个石头则移除之, 并且i递增以在下一次外层循环中跳过已经视为移除的石头
bool C(int x) { int ct = 0, tmp; for (int i = 0; i <= N; i++) {    // 这里的写法并不好, 可以写出O(n)的C(x), 参考洛谷题解区, 点击文首图片跳转 tmp = 0; for (int j = 1; j + i <= N + 1 && s[i + j] - s[i] < x; j++) tmp++; ct += tmp; i += tmp; } return ct <= M; } int main() { cin >> L >> N >> M; for (int i = 1; i <= N; i++) cin >> s[i]; s[N + 1] = L; int l = 0, r = 1000000000; while (l < r) { int mid = l + r + 1 >> 1; if (C(mid)) l = mid; else r = mid - 1; } cout << l << endl; return 0; }

在这题以外,如果以后根据实际问题写出了:(仅对于整数)

if(...)
    l = mid + 1;
else
    r = mid;

那么必须有:

int mid = l + r >> 1;

以保证不会陷入死循环.

原文地址:https://www.cnblogs.com/Gaomez/p/14156722.html