#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 5;
struct line
{
int l, r;
int w;
bool operator<(const line &l)
{
return w < l.w;
}
} a[N];
struct node
{
int l, r;
int num;
int add;
} tree[4 * N];
void build(int o, int l, int r)
{
tree[o].l = l;
tree[o].r = r;
if (l == r)
return;
int mid = (r + l) / 2;
build(2 * o, l, mid);
build(2 * o + 1, mid + 1, r);
}
void pushdown(int o)
{
if (tree[o].add)
{
tree[2 * o].num += tree[o].add;
tree[2 * o].add += tree[o].add;
tree[2 * o + 1].num += tree[o].add;
tree[2 * o + 1].add += tree[o].add;
tree[o].add = 0;
}
}
void update(int o, int l, int r, int add)
{
if (l <= tree[o].l && tree[o].r <= r)
{
tree[o].num += add;
tree[o].add += add;
return;
}
pushdown(o);
int mid = (tree[o].l + tree[o].r) / 2;
if (r > mid)
update(2 * o + 1, l, r, add);
if (l <= mid)
update(2 * o, l, r, add);
tree[o].num = min(tree[2 * o].num, tree[2 * o + 1].num);
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> a[i].l >> a[i].r >> a[i].w;
sort(a, a + n);
memset(tree, 0, sizeof(tree));
build(1, 1, m - 1);//减一的原因是将其化为区间覆盖,即覆盖1~11的区间即可
int l, r;
l = r = 0;
int ans = 1e6 + 5;
while (1)
{
while (r < n && tree[1].num < 1)
{
update(1, a[r].l, a[r].r - 1, 1);
r++;
}
if (!tree[1].num)
break;
ans = min(ans, a[r - 1].w - a[l].w);
update(1, a[l].l, a[l].r - 1, -1);
l++;
}
cout << ans << endl;
return 0;
}
尺取法
首先判断出题目可以使用尺取法进行依次推进。
思考过程
想到尺取法便有几个需要解决的问题
- 推进过程中如何确定区间的覆盖程度以及是否完成了覆盖(由于使用尺取法必须对区间按照价值排序,故无法用一个最左值和最右值来判断区间的覆盖程度);
比较朴素的想法是用一个数组来记录每一个长度为1的区间是否被覆盖,然后每次推进时更新数组,并遍历数组观察是否完成了覆盖;这种做法的复杂度是O(nm),显然复杂度过高。
此时思考一个优化的方式,观察发现使用数组来维护区间覆盖的时间消耗主要是在覆盖程度的更新与完成覆盖的验证。于是想到用线段树来维护这个过程(1.每次更新覆盖程度时不用遍历每个点,找到对应线段即可;2.如果左子树与右子树都覆盖完成,显然父节点也完成覆盖,用这个来维护线段树。)