一开始拿到这道题没有什么头绪。综合各路大佬题解,一下子豁然开朗。
题眼:每一段感受器都感受到水的最早时间。由于整个管道,分为多个段,每个段都有一个感受器。所以题眼翻译为:覆盖满整条管道,所需要的最短时间。
即:最大值最小问题——采用二分法。
思路:用二分法取时间,然后使用区间覆盖的方法来判断是否在该时刻能够覆盖满整条管道。
拿题目测试数据作为例子。
管道长度为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函数,最小为管道最左端1Q[counter].second = min(F[i].first + t, len); // 使用max函数,最多为管道最右端lencounter++;}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、管道长度lenfor (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;
}