题目链接: Boring Segments
超级推荐这个题, 感觉这个题超赞!
大致题意
给定n条线段, 每条线段覆盖区间为[l, r], 花费为c.
现在可以从中任意选择若干条线段, 使得这些线段覆盖[1, m]区间.
而我们选择这些线段的代价是: 被选择的线段中花费最大值 - 花费最小值.
问: 最小的代价是多少. (题目保证一定存在解.)
解题思路
思维
一般求极值的题目, 尺取法是很常见的一种做法.
对于本题而言, 我们希望极值之差最小, 相当于我希望选择的这若干条线段花费近可能接近.
由于我们可以从n条线段中任意选择, 因此我们不妨考虑对于所有线段按照花费从小到大排序.
接下来把排序后的所有线段依次编号1~n:
此时我们可以枚举, 当我们选择第r条线段时, 考虑l ∈ [1, r]区间的线段, l的最大合法取值是多少.
这样选择编号[l, r]的线段即可满足要求, **代价为:
c
r
−
c
l
c_r - c_l
cr−cl **.
这里枚举r的复杂度为O(n), 如果l暴力去枚举, 复杂度会爆炸.
但我们可以发现, 当r增大时, l其实是单调的, 因此我们可以用双指针的做法维护[l, r].
线段树
我们刚才已经理清了如何去进行枚举答案. 但里面有很重要的一点, 我们怎么知道此时选择线段[l, r]是否能够覆盖区间[1, m]呢?
用权值线段树维护区间覆盖情况.
即: 建立权值线段树维护区间值域, 叶节点记录每个点被覆盖次数, 树内维护当前区间的所有点中, 被覆盖的次数最少的点(维护最小值). 这样当我们想知道[1, m]区间是否已经被覆盖时, 我们可以查询根节点的最小值是否大于等于1.
接下来来说说代码细节方面:
这个题很特别的一点就是, 如果一条线段覆盖[3, 5], 另外一条覆盖[6, 10]. 我们不可以认为[3, 10]区间都被覆盖了 . 因为题目认为5和6之间是没有被覆盖的部分. 相当于线段与线段的端点必须重合.
那么我们怎么处理这样的区间覆盖问题呢?
比赛的时候我想到了个歪点子, 我认为对于一条线段覆盖[L, R], 其中[L+1, R-1]被覆盖2次, 而L和R只被覆盖一次. 每次我判断根节点的最小值是否大于等于2即可. (1号点和m号点我默认覆盖过一次了.)
然而… 傻子想法. 我先覆盖一次[1, 5], 再覆盖一次[6, 10], 然后我再覆盖一次[1, 5], 我再覆盖一次[6, 10]. 其实区间是没有被完全覆盖的. (我居然还调了半天)
我们记录线段时, 把区间[L, R]记录成[L, R-1]即可. 这样我们树内维护的区间也相应的变为[1, m-1].
(比赛时也不是没想过记录成开区间, 但是最后没想明白把自己给否掉了QAQ)
感觉这个题真的非常好, 包括但不限于: 极值–>尺取, 区间覆盖–>线段树, 区间覆盖处理方式.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E6 + 10;
struct node {
int l, r;
int val; //维护线段覆盖最小值
int lazy;
}t[N << 2];
void pushdown(node& op, int lazy) { op.val += lazy, op.lazy += lazy; }
void pushdown(int x) {
if (!t[x].lazy) return;
pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
t[x].lazy = 0;
}
void pushup(int x) { t[x].val = min(t[x << 1].val, t[x << 1 | 1].val); }
void build(int l, int r, int x = 1) {
t[x] = { l, r, 0, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int l, int r, int c, int x = 1) {
if (l <= t[x].l and r >= t[x].r) {
pushdown(t[x], c);
return;
}
pushdown(x);
int mid = t[x].l + t[x].r >> 1;
if (l <= mid) modify(l, r, c, x << 1);
if (r > mid) modify(l, r, c, x << 1 | 1);
pushup(x);
}
struct line {
int l, r, c;
bool operator< (const line& t) const { return c < t.c; }
}area[N];
int main()
{
int n, m; cin >> n >> m;
build(1, m - 1);
rep(i, n) {
int l, r, c; scanf("%d %d %d", &l, &r, &c);
area[i] = { l, r - 1, c };
}
sort(area + 1, area + 1 + n);
int res = 0x3f3f3f3f, L = 1, R = 0;
while (R + 1 <= n) {
const auto& [l, r, c] = area[++R];
modify(l, r, 1);
if (!t[1].val) continue;
while (L < R) {
const auto& [l, r, c] = area[L];
modify(l, r, -1);
if (t[1].val) ++L;
else {
modify(l, r, 1);
break;
}
}
res = min(res, area[R].c - area[L].c);
}
printf("%d\n", res);
return 0;
}