蓝桥杯管道

        一开始拿到这道题没有什么头绪。综合各路大佬题解,一下子豁然开朗。

        题眼:每一段感受器都感受到水的最早时间。由于整个管道,分为多个段,每个段都有一个感受器。所以题眼翻译为:覆盖满整条管道,所需要的最短时间。

        即:最大值最小问题——采用二分法。

        思路:用二分法取时间,然后使用区间覆盖的方法来判断是否在该时刻能够覆盖满整条管道。

拿题目测试数据作为例子。

管道长度为10,设有3个阀门,位置分别为1,6,10;开阀时间分别为1,5,2。

最短时刻取0,最长时刻取2*10(为了方便演示,就不取2倍管道最大长度了。这是易错点,解释一下为什么取2倍管道最大长度,而不取1倍管道最大长度:假设有一个管道长为1e9,仅有一个阀门,阀门位于最右侧10e9的位置,且开阀时间取到最晚为1e9。那么覆盖完整个管道,所需时间就是2*1e9。)。mid = 10,阀门1的覆盖区间为[1-(10-1),1+(10-1)],即[1,10]而非[-8,10]注意不能超过管道长度!!;阀门2的覆盖区间为[6-(10-5),6+(10-5)],即[1,10],而非[1,11]注意不能超过管道长度!!;阀门3的覆盖区间为[10-(10-2),10+(10-2)],即[2,10]。三个区间进行合并后为[1,10]满足要求。于是取mid = 5。覆盖区间分别为[1,5];[6,6];[7,10],区间合并后(注意,区间[1,5][6,6]算是成功合并了,接上了这个区间,变为[1,6],然后再与[7,10],合并后变为[1,10]),成功覆盖所有区间。mid = 2。三个区间为[1,1][6,6][10,10],合并后未能成功覆盖整条管道。mid = 3,[1,3][6,6][9,10]还是不成功,mid= 4,[1,4][6,6][8,10]也不成功。所以答案取5.

        经过上述这一番分析后,是不是就觉得很简单了,只需要编程实现就可以了。

// 看过题解后,自己写的答案
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PLLLL;
const LL N = 1e5 + 10;
PLLLL F[N];  // F记录阀门的初始数据:阀门位置、开阀时间  
PLLLL Q[N];  // Q记录阀门在t时刻水流至的区间:左端点、右端点
LL n, len;

// 判断time时刻是否能够覆盖整条管道
bool check(LL time)
{
    int counter = 0;  // 记录已开阀的数量
    // 求解每个阀门在该时刻可覆盖的区间
    for (int i = 0; i < n; i++)
    {

        if (time >= F[i].second)// 判断该时刻该阀门是否已开阀
        {  // 已开阀
            LL t = time - F[i].second;
            //Q[counter].first = F[i].first - t;  // 区间左端点
            //Q[counter].second = F[i].first + t;  // 区间右端点

            // 这里也特别容易写错成上上面这样子,我们的区间不可能超过管道的总区间呀!!!
            Q[counter].first = max((LL)1, F[i].first - t);  // 为防止左端点为负数,所以这里使用max函数,最小为管道最左端1
            Q[counter].second = min(F[i].first + t, len);  // 使用max函数,最多为管道最右端len
            counter++;
        }
        else  
        {  // 未开阀
            // 不做任何处理
        }
    }

    sort(Q, Q + counter);  // 区间合并前需要进行排序:注意,对于容器pair来说,sort第一二个参数分别为排序首元素的指针,排序末尾元素下一个地址的指针

    // 区间合并
    LL lp, rp;  // 合并后的总区间的左右端点
    lp = Q[0].first;  // 区间初始化,用第一个小区间进行初始化
    rp = Q[0].second;
    for (int i = 1; i < counter; i++)  // 遍历已开阀的阀门
    {
        if (Q[i].first <= rp + 1)  // 这里特别容易出错,举个例子[1,5][6,9]这两个区间就能够合并,所以需要+1
        {
            rp = max(rp, Q[i].second);  // 更新纵区间的右端点
        }
        else
        {  // 区间没有连接上-->存在没有水的部分,退出循环。
            break;
        }
    }

    // 判断区间是否覆盖整个管道
    return (lp == 1 && rp == len);
}
int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> len;  // 输入阀门数n、管道长度len
    for (int i = 0; i < n; i++)
    {
        cin >> F[i].first >> F[i].second;  // 输入阀门位置、打开时间
    }

    // 使用二分法判断选出覆盖整条管道所需的最小时间
    LL l = 1, r = 2e9 + 10;
    while (l < r)
    {
        LL mid = (l + r) / 2;
        if (check(mid))  // 判断该时刻是否满足覆盖要求
        {
            r = mid;
        }
        else
        {
            l = mid + 1;
        }
    }

    cout << r;
    return 0;
}

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值