洛谷 P2678 [NOIP 2015 提高组] 跳石头

题目

P2678 [NOIP 2015 提高组] 跳石头

题目背景

NOIP2015 Day2T1

题目描述

一年一度的“跳石头”比赛又要开始了!

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。


这道题目十分像贪心算法,但实际上需要类似遍历的二分答案尝试,得出最优解

为了验证我们的策略,举一个反例

贪心策略的尝试​

贪心策略的步骤是:每次找到当前最小的间隔,移除其中一块石头,合并间隔。重复此过程直到移除了 m 块石头。

​步骤 1:初始状态​
  • 石头位置:[0, 2, 3, 5, 7, 10]
  • 间隔:[2, 1, 2, 2, 3](即 2-0=23-2=15-3=27-5=210-7=3
  • ​最小间隔是 1​​(位置 2 和 3 之间)。
​操作 1:移除位置 3 的石头​
  • 移除后石头位置:[0, 2, 5, 7, 10]
  • 新间隔:[2, 3, 2, 3](因为 5-2=3
  • ​新的最小间隔是 2​​(例如 5-7=2)。
​操作 2:移除位置 5 的石头​
  • 移除后石头位置:[0, 2, 7, 10]
  • 新间隔:[2, 5, 3]
  • ​最终最短距离是 2​​。

​更优的解法​

如果我们选择不同的移除策略:

  • ​移除位置 2 和 7 的石头​​:
    • 移除后石头位置:[0, 3, 5, 10]
    • 间隔:[3, 2, 5]
    • ​最短距离是 2​​(似乎和贪心结果相同,但让我们再优化)。

或者:

  • ​移除位置 3 和 7 的石头​​:
    • 移除后石头位置:[0, 2, 5, 10]
    • 间隔:[2, 3, 5]
    • ​最短距离是 2​​。

但如果我们允许更大的间隔:

  • ​移除位置 2 和 5 的石头​​:
    • 移除后石头位置:[0, 3, 7, 10]
    • 间隔:[3, 4, 3]
    • ​最短距离是 3​​(明显更优!)

      以上的反例证明,贪心解并不能获得最优解
       

 那么我们这时候开始选择二分答案近乎遍历的思想

正确方法:二分答案​

正确的思路是使用二分法,直接猜测最短距离 d,然后检查是否可以通过移除 ≤m 块石头使得所有间隔都 ≥d。例如:

  • 猜测 d = 3,检查是否可行:
    • 初始位置 [0, 2, 3, 5, 7, 10]
    • 维护最后一个保留的位置 prev = 0
    • 2 - prev = 2 < 3 → 必须移除位置 2。
    • 3 - prev = 3 ≥ 3 → 保留位置 3,prev = 3
    • 5 - prev = 2 < 3 → 必须移除位置 5。
    • 7 - prev = 4 ≥ 3 → 保留位置 7,prev = 7
    • 10 - prev = 3 ≥ 3 → 保留。
    • 移除了位置 2 和 5(共 2 块),满足条件。最短距离 3 是可行的!

举这个算法的例子,让同学们更能理解此算法的本质

是通过验证是否可以在一定的移除数量内得出相符合答案,依此再进行迭代,让答案逐渐达到最优解。

那么为什么是一定数量内,因为当小于该固定数量就可以满足条件时,再减少的石头,一定会增大距离。

无论具体问题如何变化(如石头问题、牛栏问题、书籍分配问题),只要满足单调性,都可以套用以下模板:

low = 最小可能答案
high = 最大可能答案
while low <= high:
    mid = (low + high) / 2
    if check(mid) 可行:
        尝试更大的答案 (low = mid + 1)
    else:
        尝试更小的答案 (high = mid - 1)
最终答案 = high (或记录过程中的最优值)

源码如下 

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long

bool check(const vector<ll>& stones, ll d, ll m) {
    ll prev = 0;
    ll count = 0;
    for (int i = 1; i < stones.size(); ++i) {
        if (stones[i] - prev < d) {
            ++count;
            if (stones[i] == stones.back()) { // 终点无法移除,
                break;
            }
        }
        else {
            prev = stones[i];
        }
    }
    return count <= m;
}

int main() {
    ll l, n, m;
    cin >> l >> n >> m;

    vector<ll> stones(n + 2);
    stones[0] = 0;
    for (ll i = 1; i <= n; ++i) {
        cin >> stones[i];
    }
    stones[n + 1] = l;
    sort(stones.begin(), stones.end());

    ll low = 1, high = l;
    ll ans = 0;
    while (low <= high) {
        ll mid = (low + high) / 2;
        if (check(stones, mid, m)) {
            ans = mid;
            low = mid + 1;
        }
        else {
            high = mid - 1;
        }
    }
    cout << ans << endl;
    return 0;
}

 

 感谢观看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值