E. Boring Segments
题目大意
给定一个数轴,\(1\to m\),以及\(m\)个线段的\(l_i, r_i, w_i\),为线段的左端点,右端点,以及价值。
现在要求选择一些线段,使得能够从数轴上的\(1\)出发,沿着线段走,能够到达\(m\)。
代价是选择的线段的价值的极差。
问最小的代价值。
解题思路
我们将线段按照价值从小到大排序,然后依次枚举价值最小的线段。
然后不断地往右取线段,直到能够从\(1\)走到\(m\),此时即为一个答案。
随着价值最小的线段不断右移,价值最大的线段也在不断右移,所谓的双指针法。
此时只要考虑如何维护选择的线段覆盖的信息。
由于\(m\leq 10^6\),维护每个数字被线段覆盖的次数,用线段树维护即可,区间加法和区间最小值。
注意到仍存在一点问题,即如果一条线段覆盖\(1,2,3\),另一条线段覆盖\(4,5\),此时从\(1\)不能达到\(5\),我们还需要一条线段覆盖\(3,4\),也就是说3,4之间再插入一个点。
因此将所有坐标都乘以\(2\),为每个数字之间都插入一个点,然后再区间加法和区间查询最小值即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 8;
class Segment{
#define lson root << 1
#define rson root << 1 | 1
int minn[N << 2];
int lazy[N << 2];
public:
void pushdown(int root){
if (lazy[root] == 0)
return;
minn[lson] += lazy[root];
minn[rson] += lazy[root];
lazy[lson] += lazy[root];
lazy[rson] += lazy[root];
lazy[root] = 0;
}
void update(int root, int l, int r, int ll, int rr, int val){
if (ll <= l && r <= rr){
minn[root] += val;
lazy[root] += val;
return;
}
pushdown(root);
int mid = (l + r) >> 1;
if (ll <= mid)
update(lson, l, mid, ll, rr, val);
if (rr > mid)
update(rson, mid + 1, r, ll, rr, val);
minn[root] = min(minn[lson], minn[rson]);
}
inline bool query(){
return minn[1] != 0;
}
}Seg;
int n, m;
pair<pair<int,int>, int> s[N];
int id[N];
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
m <<= 1;
for(int i = 1; i <= n; ++ i){
cin >> s[i].first.first >> s[i].first.second >> s[i].second;
s[i].first.first <<= 1;
s[i].first.second <<= 1;
id[i] = i;
}
sort(id + 1, id + 1 + n, [&](int x, int y){
return s[x].second < s[y].second;
});
int cur = 1;
int ans = 1e9 + 7;
for(int i = 1; i <= n; ++ i){
while(cur <= n && !Seg.query()){
Seg.update(1, 2, m, s[id[cur]].first.first, s[id[cur]].first.second, 1);
++ cur;
}
if (Seg.query())
ans = min(ans, s[id[cur - 1]].second - s[id[i]].second);
Seg.update(1, 2, m, s[id[i]].first.first, s[id[i]].first.second, -1);
}
cout << ans << '\n';
return 0;
}