原题:
E. Boring Segments
time limit per test
3 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output
You are given n
segments on a number line, numbered from 1 to n. The i-th segments covers all integer points from li to ri and has a value wi.
You are asked to select a subset of these segments (possibly, all of them). Once the subset is selected, it’s possible to travel between two integer points if there exists a selected segment that covers both of them. A subset is good if it’s possible to reach point m
starting from point 1 in arbitrary number of moves.
The cost of the subset is the difference between the maximum and the minimum values of segments in it. Find the minimum cost of a good subset.
In every test there exists at least one good subset.
Input
The first line contains two integers n
and m (1≤n≤3⋅10^5; 2≤m≤10^6) — the number of segments and the number of integer points.
Each of the next n lines contains three integers li, ri and wi (1≤li<ri≤m; 1≤wi≤10^6) — the description of the i-th segment.
In every test there exists at least one good subset.
Output
Print a single integer — the minimum cost of a good subset.
Examples
Input
5 12
1 5 5
3 4 10
4 10 6
11 12 5
10 12 3
Output
3
Input
1 10
1 10 23
Output
0
中文:
给你n个区间,每个区间的端点分别为
l
i
,
r
i
l_i,r_i
li,ri,区间的权重是
w
i
w_i
wi。
现在让你选其中的一些区间,使得这些区间能够覆盖[1,m],并且选定的这些区间中的最大权重减去最小权重的值最小。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 5;
int n,m;
struct node {
int l;
int r;
int w;
};
node ns[maxn];
int val[maxn << 2];
int mark[maxn << 2];
void pushUp(int rt) {
val[rt] = min(val[rt << 1], val[rt << 1|1]);
}
void pushDown(int rt) {
if (mark[rt]) {
mark[rt << 1] += mark[rt];
mark[rt << 1|1] += mark[rt];
val[rt << 1] += mark[rt];
val[rt << 1|1] += mark[rt];
mark[rt] = 0;
}
}
void update(int st, int ed, int v, int L, int R, int rt) {
if (L >= st && R <= ed) {
mark[rt] += v;
val[rt] += v;
return;
}
pushDown(rt);
int M = (L+R) >> 1;
if (st <= M) {
update(st, ed, v, L, M, rt << 1);
}
if (ed > M) {
update(st, ed, v, M + 1, R, rt << 1 | 1);
}
pushUp(rt);
}
bool cmp(const node& lhs, const node& rhs) {
return lhs.w < rhs.w;
}
int main()
{
ios::sync_with_stdio(false);
while (cin >> n >> m) {
memset(mark,0 ,sizeof(mark));
memset(val, 0 ,sizeof(val));
int l,r,w;
m--;
for (int i=1;i<=n;i++) {
cin>> ns[i].l >> ns[i].r >> ns[i].w;
ns[i].r--;
}
sort(ns + 1, ns + 1 +n, cmp);
int ans = INT_MAX;
for (int rp = 1, lp = 1; rp<=n; rp++) {
update(ns[rp].l, ns[rp].r, 1, 1, m, 1);
while(val[1] > 0) {
ans = min(ans, ns[rp].w - ns[lp].w);
update(ns[lp].l, ns[lp].r, -1, 1, m, 1);
lp++;
}
}
cout << ans << endl;
}
return 0;
}
解答:
这题首先会想到对线段安装权重排序,假设一共有n个线段,假设取了排序后第i个到第j个线段。
那么,权重的最大值减去最小值明显就是
w
j
−
w
i
w_j-w_i
wj−wi,那这第i个到第j个线段能否覆盖[1,m]吗?
区间上动态判断线段覆盖问题问题肯定是用线段树了,对选定的i到j个线段所覆盖的区域的每个点用加1的方式计数,最后判断[1,m]中所有点计数的最小值是否是大于等于1的,如果满足,则可以断定i到j个线段是可以覆盖全部[1,m];如果[1,m]所有点计数的最小值是0,说明存在一些点没有被选定的i到j个线段覆盖到。
接下来考虑如何枚举区间?
从给定的数据量来看,n^2的暴力是不行的。
这里使用尺取的方法来选定线段的集合,假设两个下标i和j,表示当前已经选定了第i个下标到第j个下标之间的线段。并且,此时通过线段树判断,满足将[1,m]覆盖的要求。根据尺取法的策略,将下标i进行递增,递增的同时判断新的i到j之间的线段是否可以覆盖[1,m],同时更新最大的权重减去最小的权重的值。
如果选定的i到j个线段没有办法覆盖[1,m],那么j递增,直到满足i到j个线段可以覆盖[1,m],保留此时的权重差值即可。