管道(acwing,蓝桥杯,二分)

题目描述:

有一根长度为 len 的横向的管道,该管道按照单位长度分为 len 段,每一段的中央有一个可开关的阀门和一个检测水流的传感器。

一开始管道是空的,位于 Li的阀门会在 Si 时刻打开,并不断让水流入管道。

对于位于 Li的阀门,它流入的水在 Ti(Ti≥Si)时刻会使得从第 Li−(Ti−Si) 段到第 Li+(Ti−Si)段的传感器检测到水流。

求管道中每一段中间的传感器都检测到有水流的最早时间。

输入格式:

输入的第一行包含两个整数 n,len,用一个空格分隔,分别表示会打开的阀门数和管道长度。

接下来 n行每行包含两个整数 Li,Si,用一个空格分隔,表示位于第 Li 段管道中央的阀门会在 Si 时刻打开。

输出格式:

输出一行包含一个整数表示答案。

数据范围:

对于 30%的评测用例,n≤200,Si,len≤3000;
对于 70%的评测用例,n≤5000,Si,len≤1e5;
对于所有评测用例,1≤n≤1e5,1≤Si,len≤1e9,1≤Li≤len,Li−1<Li。

输入样例:

3 10
1 1
6 5
10 2

输出样例:

5

分析步骤:

  第一:我们拿到这道题我们可以看到,是一段管道里有许多的水龙头,当开启不同水龙头的时候它可以向两边扩散速度是:1段/s。不同水龙头控制的区域也不一样,至此我们分析出了第一个特点有区间问题,需要合并看看有没有全部覆盖管道

            我们还可以发现当时间开的越长,管道内有水的区域也越长,这呈现一种单调的特点,并且我们可以求出一个时间点,在这个时间点的左边管道内不能全部都流过水,在这个时间点和以后都可以覆盖全管道。至此也满足了二分的特点

            所以本题要运用二分+区间合并来做;

 

第二:知道了考的知识点,咱们就要书写主函数,把握整体框架。

           首先把值都输入进去,其次开始二分

            定义我们的左节点 l 为 0 因为有可能水管从第零分钟就一直开着,并且每个管道段落都有水龙头,所以最小时间可能是 0

             定义右节点 r 为 2e9 .因为题目说了 Si 最大为 1e9  所以有可能在最后一秒也就是1e9秒时 ,开启开关并且只有一个开关且在第一个管道之内,所以要覆盖管道又要1e9s 所以两个 1e9 相加就是 2e9。

           求出mid,再判断一下这个 mid 的值

            如果是对的,则证明比 mid 的时间大小更大的值就更可以覆盖满管道.因为假如 mid 为5秒时已经覆盖了管道,那么6秒更会是管道内充水。所以应该将 mid 赋值给 r

            如果 mid 的时间大小不可以,那么比mid还小的话更是不可能覆盖的了管道了。所以应该将 mid + 1 的值 赋值给 l;

int main()
{
    cin>>n>>m;
    for(int i = 0 ; i < n ; i ++){
        cin>>q[i].x>>q[i].y;
    }
    LL l = 0 , r = 2e9;
    while(l<r){
        LL mid = (l+r) / 2;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout<<r;
    return 0;
}

  第三:书写 check 函数定义一个 计数器 cnt 用一个f or 循环将之前存储的数据取出来,如果mid这个时间大于了我们的S这个时间的话,那么就意味着这个开关已经开启了并且流出了一段的水,这段时间的差值 就是这个开关向左右两边扩散的距离,所以我们用 t 计算出这段时间差。

          确定左右区间:

           我们接下来看这个开关可以控制的区间范围因为 t 就是扩散的距离 所以我们的 int l = max(1, L-t ) ,左边界范围就是第 L 个开关向左走了 t 时间的路 和 1 取最大值进行动态更新

            r = min((LL)m,(LL)L+t);右边界范围就是第 L 个开关向左走了 t 时间的路 和 m 取最小值进行动态更新因为不可以超过管道长度。

            最后将这个开关的区间边界放入 p 数组之中;至此我们区间范围已然确定。

int cnt = 0 ; 
     for(int i = 0 ; i < n ; i ++){
         int L = q[i].x , S = q[i].y;
         if(mid >= S){
             int t = mid - S;
             int l = max(1,L-t) , r = min((LL)m,(LL)L+t);
                  p[cnt++]={l,r};
         }
     }

  第四:接下来进行区间合并。我们先进行sort排序这样这些区间的左端点就可以从小到大排序,我们就只要比较右端点就行。

           我们定义左端点 st 为 -1 ; 右端点ed 为 -1 

           如果 我们的左端点st <= 上一个存值的区间右端点 加 1 的话我们就只需要更新一下我们右节点 ed 就可以把两个区间合并了。为什么要加1?直接小于等于 ed 不行吗? 不可以 。因为ed那里已经有水了,只是 ed+1 的那个点没有水,所以如果我们的 l == ed+1的话也是可以算连接上了的。

          如果 我们的左端点st > 上一个存值的区间右端点 加 1 的话 就证明这两个区间必有间隙则更改我们的st 和 ed为新的区间端点。对于这道题目,如果st上一个存值的区间右端点 加 1,我们可以直接返回return st == 1 and ed == m;看看是否符合,如果符合则充满了管道,如果不符合,我们sort排过序了,那么这道题目有空隙的话,那么一定就是错了。

         最终返回判断一下st是否为 1 并且 ed是否为管道长度。如果都符合就返回true,有一个不符合就是false;

            


     sort(p,p+cnt);
     int ed = -1 , st = -1;
     for(int i = 0 ; i < cnt ; i ++){
        if(p[i].x <= ed + 1 ) ed = max(ed , p[i].y);
        else return st == 1 and ed == m;
     }
    return st == 1 and ed == m;

  

注意:区间合并就三种情况相交里包含两种,还有一种相离

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e5+10;
PII q[N] , p[N];
LL n , m;


bool check(int mid){
     int cnt = 0;
     for(int i = 0 ; i < n ; i ++){
         int L = q[i].x ,  S = q[i].y;
         if(mid >= S){
             int t = mid - S;
             int l = max(L - t , 1) , r = min( (LL)L + t  , (LL)m) ;
             p[cnt ++ ] = {l,r};
         }
     }
     sort(p , p+cnt);
     int st = p[0].x, ed = p[0].y; 
    for(int i = 1 ; i < cnt ; i ++){
        if(p[i].x <= ed + 1) ed = max(ed , p[i].y);
        else return st == 1 and ed == m;
    }
    return st == 1 and ed == m;
}


int main()
{
    cin>>n>>m;
    for(int i = 0 ; i < n ; i ++){
        cin>>q[i].x>>q[i].y;
    }
    LL l = 0 , r = 2e9;
    while(l < r){
        LL mid = (l + r)/2;
        if(check(mid)) r = mid; 
        else l = mid + 1  ;
    }
    cout<<r;
    return 0;
}

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值