当前位置:首页 > 题解目录 > 正文内容

【题解】河中跳房子

【题目描述】

每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一个岩石跳到另一个岩石。这项激动人心的活动在一条长长的笔直河道中进行,在起点和离起点L远 (1 ≤ L≤ 1,000,000,000) 的终点处均有一个岩石。在起点和终点之间,有N (0 ≤ N ≤ 50,000) 个岩石,每个岩石与起点的距离分别为Di (0 < Di < L)。

在比赛过程中,奶牛轮流从起点出发,尝试到达终点,每一步只能从一个岩石跳到另一个岩石。当然,实力不济的奶牛是没有办法完成目标的。

农夫约翰为他的奶牛们感到自豪并且年年都观看了这项比赛。但随着时间的推移,看着其他农夫的胆小奶牛们在相距很近的岩石之间缓慢前行,他感到非常厌烦。他计划移走一些岩石,使得从起点到终点的过程中,最短的跳跃距离最长。他可以移走除起点和终点外的至多M (0 ≤ M ≤ N) 个岩石。

请帮助约翰确定移走这些岩石后,最长可能的最短跳跃距离是多少?

【输入描述】

第一行包含三个整数L, N, M,相邻两个整数之间用单个空格隔开。

接下来N行,每行一个整数,表示每个岩石与起点的距离。岩石按与起点距离从近到远给出,且不会有两个岩石出现在同一个位置。

【输出描述】

一个整数,最长可能的最短跳跃距离。


【样例输入】

25 5 2
2
11
14
17
21

【样例输出】

4

【题目分析】

这是一道 “二分+贪心”的典型例题


  1. 问题转化成下面这样:

我们有一条长度为L的河道,起点在位置0,终点在位置L。河道中有N个岩石,位置分别是给定的。奶牛需要从起点跳到终点,每次跳跃必须从一个岩石跳到另一个岩石。农夫约翰可以移除最多M个岩石(不包括起点和终点),目标是使得在所有可能的跳跃方案中,最短的跳跃距离尽可能大。

换句话说,我们需要找到一个最大的最短跳跃距离D,使得在移除不超过M个岩石后,剩下的岩石(包括起点和终点)之间的所有相邻跳跃距离都不小于D。


2. 为什么是二分?

因为我们需要找到最大的D,使得在移除不超过M个岩石后,所有相邻跳跃距离≥D。这是一个典型的“最大化最小值”问题,通常可以用二分查找来解决。
二分查找的思路

(1)确定搜索范围:D的最小可能值是0(如果移除所有岩石,直接从起点跳到终点),最大可能值是L(起点到终点的距离)。
(2)二分查找:
    选择一个中间的D值(mid)。 
    检查是否可以移除不超过M个岩石,使得所有相邻跳跃距离≥mid。
    如果可以,说明D可以更大,所以调整左边界。
    如果不可以,说明D需要更小,所以调整右边界。
(3)终止条件:当左边界和右边界相遇时,找到最大的D。



3.如何验证D是否可行

编写一个函数isPossible(D),判断是否可以移除不超过M个岩石,使得所有相邻跳跃距离≥D。具体步骤如下:
(1)初始化:
    prev = 0(起点位置)。
    count = 0(移除的岩石数量)。
(2)遍历所有岩石:
    对于每个岩石,计算它与prev的距离。
    如果距离 < D,说明需要移除该岩石(count++)。
    否则,保留该岩石,更新prev为当前岩石。
(3)检查终点:
    计算终点与最后一个保留的岩石的距离。
    如果距离 < D,可能需要移除更多岩石(但终点不能移除,所以可能需要调整)。
    返回count ≤ M。



4.例子



岩石位置:[2, 11, 14, 17, 21],起点0,终点25,可以移除最多2个岩石。

二分查找过程:

  1. 初始:left = 0right = 25

  2. mid = 12

    • 起点0,第一个岩石2:距离2 < 12,移除2(count=1)。

    • 下一个岩石11:距离11-0=11 < 12,移除11(count=2)。

    • 下一个岩石14:距离14-0=14 ≥ 12,保留14(prev=14)。

    • 下一个岩石17:距离17-14=3 < 12,移除17(count=3 > M=2)。

    • 不可行。

    • 验证isPossible(12)

    • 调整right = 11

  3. mid = 5

    • 起点0,岩石2:距离2 < 5,移除2(count=1)。

    • 岩石11:距离11-0=11 ≥ 5,保留11(prev=11)。

    • 岩石14:距离14-11=3 < 5,移除14(count=2)。

    • 岩石17:距离17-11=6 ≥ 5,保留17(prev=17)。

    • 岩石21:距离21-17=4 < 5,移除21(count=3 > M=2)。

    • 不可行。

    • 验证isPossible(5)

    • 调整right = 4

  4. mid = 2

    • 起点0,岩石2:距离2 ≥ 2,保留2(prev=2)。

    • 岩石11:距离11-2=9 ≥ 2,保留11(prev=11)。

    • 岩石14:距离14-11=3 ≥ 2,保留14(prev=14)。

    • 岩石17:距离17-14=3 ≥ 2,保留17(prev=17)。

    • 岩石21:距离21-17=4 ≥ 2,保留21(prev=21)。

    • 终点25:距离25-21=4 ≥ 2。

    • count=0 ≤ M=2。

    • 可行。

    • 验证isPossible(2)

    • 调整left = 3

  5. mid = 3

    • 起点0,岩石2:距离2 < 3,移除2(count=1)。

    • 岩石11:距离11-0=11 ≥ 3,保留11(prev=11)。

    • 岩石14:距离14-11=3 ≥ 3,保留14(prev=14)。

    • 岩石17:距离17-14=3 ≥ 3,保留17(prev=17)。

    • 岩石21:距离21-17=4 ≥ 3,保留21(prev=21)。

    • 终点25:距离25-21=4 ≥ 3。

    • count=1 ≤ M=2。

    • 可行。

    • 验证isPossible(3)

    • 调整left = 4

  6. mid = 4

    • 起点0,岩石2:距离2 < 4,移除2(count=1)。

    • 岩石11:距离11-0=11 ≥ 4,保留11(prev=11)。

    • 岩石14:距离14-11=3 < 4,移除14(count=2)。

    • 岩石17:距离17-11=6 ≥ 4,保留17(prev=17)。

    • 岩石21:距离21-17=4 ≥ 4,保留21(prev=21)。

    • 终点25:距离25-21=4 ≥ 4。

    • count=2 ≤ M=2。

    • 可行。

    • 验证isPossible(4)

    • 调整left = 5

  7. mid = 5

    • 之前验证过不可行。

    • 调整right = 4

  8. 终止:left = 4right = 4,输出4。

为什么输出是4?

因为在移除岩石2和14后,剩下的岩石是[11, 17, 21],跳跃距离为:

  • 0 → 11:11

  • 11 → 17:6

  • 17 → 21:4

  • 21 → 25:4 最短跳跃距离是4,且移除的岩石数量是2 ≤ M=2。无法通过移除≤2个岩石得到更大的最短跳跃距离。









【参考答案】

#include<bits/stdc++.h>
using namespace std;
int rocks[100000];//假设岩石数量不超过 100000

// 判断是否可以通过移除最多 M 块岩石,使得最小跳跃距离至少为 D
bool isPossible(int D, int N, int L, int M) {
    int prev = 0; // 起点
    int count = 0;
    for (int i = 0; i < N; i++) {
        if (rocks[i] - prev < D) {
            count++; // 移除当前岩石
        } else {
            prev = rocks[i]; // 保留当前岩石
        }
        if (count > M) return false;
    }
    // 检查终点
    if (L - prev < D) {
        count++; // 需要移除最后一个岩石(但终点不能移除)
        if (count > M) return false;
    }
    return count <= M;
}

int main() {
    int L, N, M;
    cin>>L>>N>>M;
    for (int i = 0; i < N; i++) {
        cin>>rocks[i];
    }
    // 二分查找
    int left = 0, right = L;
    int ans = 0;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (isPossible(mid, N, L, M)) {
            ans = mid;
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
	cout<<ans;
    return 0;
}


扫描二维码推送至手机访问。

版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

分享给朋友:

相关文章

【题解】月度开销

【题目描述】农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来N(1 ≤N≤ 100,000) 天里每天需要的开销。约翰打算为连续的M(1 ≤M≤N)...

【NOIP2000】计算器的改良

【题目描述】NCL 是一家专门从事计算器改良与升级的实验室,最近该实验室收到了某公司所委托的一个任务:需要在该公司某型号的计算器上加上解一元一次方程的功能。实验室将这个任务交给了一个刚进入的新手 ZL...

剪刀石头布

【题目描述】石头剪子布,是一种猜拳游戏。起源于中国,然后传到日本、朝鲜等地,随着亚欧贸易的不断发展它传到了欧洲,到了近现代逐渐风靡世界。简单明了的规则,使得石头剪子布没有任何规则漏洞可钻,单次玩法比拼...

字符全排列

【题目描述】给定一个由不同的小写字母组成的字符串,输出这个字符串的所有全排列。我们假设对于小写字母有‘a’ <‘b’ < … <‘y’<‘z’,而且给定的字符串中的字母已经按照...

文具订购(NOI online入门组)

【题目描述】小明的班上共有n元班费,同学们准备使用班费集体购买3种物品。圆规,每个7元。笔,每支4元。笔记本,每本3元。小明负责订购文具,设圆规、笔、笔记本的订购数量为a,b,c,他订购的原则依次如下...

【题解】求车速问题

【题目描述】一辆以固定速度行驶的汽车,司机在上午10点看到里程表上的读数是一个对称数(即这个数从左向右读和从右向左读是完全一样的),为95859。两小时后里程表上出现了一个新的对称数。问该车的速度是多...