题解:洛谷 P3853 路标设置

【题目来源】

洛谷:P3853 [TJOI2007] 路标设置 - 洛谷

【题目描述】

现在政府决定在公路上增设一些路标,使得公路的“空旷指数”最小。他们请求你设计一个程序计算能达到的最小值是多少。请注意,公路的起点和终点保证已设有路标,公路的长度为整数,并且原有路标和新设路标都必须距起点整数个单位距离。

【输入】

\(1\) 行包括三个数 \(L,N,K\),分别表示公路的长度,原有路标的数量,以及最多可增设的路标数量。

\(2\) 行包括递增排列的 \(N\) 个整数,分别表示原有的 \(N\) 个路标的位置。路标的位置用距起点的距离表示,且一定位于区间 \([0,L]\) 内。

【输出】

输出 \(1\) 行,包含一个整数,表示增设路标后能达到的最小“空旷指数”值。

【输入样例】

101 2 1
0 101

【输出样例】

51

【核心思想】

  1. 问题分析:给定公路长度 \(L\),原有 \(N\) 个路标位置,最多增设 \(K\) 个路标。"空旷指数"定义为相邻路标间的最大距离。求增设 \(K\) 个路标后的最小空旷指数。这是一个整数二分 + 贪心验证问题,关键在于二分答案(空旷指数),验证给定空旷指数下是否需要不超过 \(K\) 个新增路标。

  2. 算法选择

    • 整数二分:在答案范围 \([1, L]\) 内二分查找最小可行的空旷指数
    • 贪心验证:对于给定的空旷指数 \(mid\),计算每段原有间距需要增设多少个路标(即 ceil(a[i] / mid) - 1),累加判断是否 \(\leq K\)
  3. 关键步骤

    • 读入数据\(L, N, K\)\(N\) 个路标位置
    • 计算间距a[i] = now - last,最后一段 a[N] = L - last
    • 二分查找\(l = 1, r = 10^7\)):
      • mid = l + (r - l) / 2
      • count = 0:需要增设的路标数
      • 遍历每段间距 a[i]
        • while (tmp > mid)count++tmp -= mid(模拟增设路标分割间距)
      • count <= Kr = mid - 1(可以尝试更小的空旷指数)
      • 否则:l = mid + 1(需要更大的空旷指数)
    • 输出 l
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log L)\),二分 \(O(\log L)\),每次验证 \(O(N)\)
    • 空间复杂度:\(O(N)\),存储间距数组
  5. 整数二分 + 贪心验证的核心思想

    • 答案单调性:若空旷指数 \(mid\) 可行(需要增设的路标 \(\leq K\)),则所有 \(> mid\) 的值也可行;若不可行,则所有 \(< mid\) 的值也不可行。这种单调性支撑二分查找
    • 贪心分割策略:对于一段长度为 \(d\) 的间距,若目标空旷指数为 \(mid\),则至少需要 ceil(d / mid) - 1 个新增路标将其分割为每段 \(\leq mid\) 的小段。贪心地在每段均匀分割是最优的
    • 二分边界处理:循环结束时 l 是第一个不可行的值?不对,仔细看:当 count <= Kr = mid - 1,当 count > Kl = mid + 1。最终 l 是使 count > K 的最小值?实际上,这是寻找下界的标准写法,最终 l 是满足条件的最小值
    • 适用于"最小化最大值"类问题,核心在于二分答案和贪心验证

【解题思路】

【算法标签】

普及 #整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 全局变量:
// length: 道路总长度
// n: 已有石头数量
// k: 可以移除的石头数量
// a[100005]: 存储相邻石头间的距离
// last: 记录前一个石头位置
// now: 当前石头位置
// l, r: 二分查找的左右边界
int length, n, k, a[100005], last, now, l, r;

int main()
{
    // 输入道路总长度、已有石头数量和可移除石头数量
    cin >> length >> n >> k;

    last = 0;  // 初始化前一个石头位置为起点0

    // 计算相邻石头间的距离
    for (int i = 0; i < n; i++)
    {
        cin >> now;
        a[i] = now - last;  // 计算当前石头与前一个石头的距离
        last = now;         // 更新前一个石头位置
    }

    // 添加终点到最后一个石头的距离
    a[n] = length - last;

    // 特殊情况处理:不能移除任何石头时,直接返回道路总长度
    if (k == 0)
    {
        cout << length;
        return 0;
    }

    // 初始化二分查找边界
    l = 1;      // 最小跳跃距离至少为1
    r = 1e7;    // 设置一个足够大的上界

    // 二分查找最小化最大跳跃距离
    while (l <= r)
    {
        int mid = l + (r - l) / 2;  // 计算中间值,防止溢出
        int count = 0;              // 记录需要移除的石头数量

        // 计算在当前mid值下需要移除多少石头
        for (int i = 0; i <= n; i++)
        {
            int tmp = a[i];  // 当前石头间距
            while (tmp > mid) // 如果间距大于mid,需要分割
            {
                count++;     // 增加移除石头计数
                tmp -= mid;  // 模拟移除石头后的剩余距离
            }
        }

        // 根据计算结果调整搜索范围
        if (count <= k)
        {
            r = mid - 1;  // 可以尝试更小的最大跳跃距离
        }
        else
        {
            l = mid + 1;  // 需要更大的最大跳跃距离
        }
    }

    // 输出最终确定的最小化最大跳跃距离
    cout << l << endl;

    return 0;
}

【运行结果】

101 2 1
0 101
51
posted @ 2026-08-11 11:27  团爸讲算法  阅读(25)  评论(0)    收藏  举报