蓝桥:管道(python,二分法,区间合并)

问题描述:

有一根长度为 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≤105;
对于所有评测用例,1≤n≤10
5,1≤Si,len≤10**9,1≤Li≤len,Li−1<Li。

输入样例:

3 10
1 1
6 5
10 2

输出样例:

5

思路:

这道题是14届蓝桥省赛pythonB组的题,自己做的时候没有AC,题目意思很好理解,但不知道如何处理阀门在不同时刻的打开问题。

这道题需要用到二分和区间合并的思想,二分法是用来给自己多增加一个判断条件。

二分法:

对于所有数据而言,管道最长为10**9,所以可以设定L为0, R为2 * 10 **9,这里的L和R不是代表管道长度,而是代表时刻,因为一个管道单位等于一时间单位。

mid表示在mid时刻,管道中是否有水,若有水,则最短时刻应该在mid的左边,若无水则最短时刻应该在mid的右边。

这里可能会绕一下,先不用着急,mid有水就要找有水和无水的交界点,这个交界点才是最短时刻,mid无水也是同样的道理,这里只是二分判断,阀门开水还在后面。

l = 0
r = 2 * 10 ** 9
while l <= r:
    mid = (l + r) // 2
    if check(mid):
        r = mid - 1
    else:
        l = mid + 1

接下来是区间合并

区间合并用来判断mid是否有水,此时的t时刻等于mid,如果阀门打开的时刻s 大于t,说明在t时刻时,该阀门未被打开,则跳过该阀门去找其他阀门

    for l, s in path:
        if s > t:
            continue

把所有在mid时刻内的阀门都找出来(这个很简单,一个for循环就可以找出),并求出每个阀门的左右区间(在t时刻时,水流流过的管道区间)

每个管道单方向上在t时刻都扩张了(t - s)区间,因为阀门是在s时刻打开的,所以t时刻时,单方向上流了(t - s)区间

对于左方向,左边界不得超出管道边界,所以要在1 和 L - (t - s) 上取最大值,L表示该阀门所处的管道位置。left = max(1, L - (t - s))

left = max(1, l - (t - s))

对于右方向,同理,right = min(len_n, L + (t -s)) , len_n 表示管道长度

right = min(len_n, l + (t - s))

把所有打开的阀门的区间放进数组中进行排序,看这些区间是否把管道的各个位置都进行了覆盖,如果是则表示mid时刻管道内都有水,否则,管道内没水。

    # 对vis排序
    vis.sort()
    # 判断是否满足条件
    if vis[0][0] != 1:
        return False
    right = vis[0][1]
    for i in range(1, len(vis)):
        if vis[i][0] > right + 1:
            return False
        else:
            right = max(right, vis[i][1])
    if right != len_n:
        return False
    return True

代码及详细注释:

import os
import sys

# 输入n和len_n
n, len_n = map(int, input().split())
path = []
# 读取每个路径的长度和起点位置
for i in range(n):
    li, si = map(int, input().split())
    path.append([li, si])

# 定义函数check,用于检查是否满足条件
def check(t):
    vis = []
    # 遍历每个路径
    for l, s in path:
        if s > t:
            continue
        else:
            left = max(1, l - (t - s))
            right = min(len_n, l + (t - s))
            vis.append([left, right])
    
    # 对vis排序
    vis.sort()
    # 判断是否满足条件
    if vis[0][0] != 1:
        return False
    right = vis[0][1]
    for i in range(1, len(vis)):
        if vis[i][0] > right + 1:
            return False
        else:
            right = max(right, vis[i][1])
    if right != len_n:
        return False
    return True

# 二分查找最小的满足条件的时间t
l = 0
r = 2 * 10 ** 9
while l <= r:
    mid = (l + r) // 2
    if check(mid):
        r = mid - 1
    else:
        l = mid + 1
print(l)

总结:

复盘一遍后发现本题其实并不难,但是这个二分思想说实话很难想到

  • 34
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不染_是非

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值