Loading

P1712 [NOI2016] 区间(双指针/线段树)

题目描述

在数轴上有 n个闭区间从 1 至 n 编号,第 i 个闭区间为 [li,ri][l**i,r**i] 。

现在要从中选出 m 个区间,使得这 m 个区间共同包含至少一个位置。换句话说,就是使得存在一个 x ,使得对于每一个被选中的区间 [li,ri][l**i,r**i],都有 li≤x≤ri。

对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。

区间 [li,ri][l**i,r**i] 的长度定义为 (ri−li) ,即等于它的右端点的值减去左端点的值。

求所有合法方案中最小的花费。如果不存在合法的方案,输出 −1。

输入格式

第一行包含两个整数,分别代表 n 和 m。

第 22 到第 (n+1) 行,每行两个整数表示一个区间,第 (i+1) 行的整数 li,ri 分别代表第 i 个区间的左右端点。

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1复制

6 3
3 5
1 2
3 4
2 2
1 5
1 4

输出 #1复制

2

看到题目说花费为“最长区间长度减去被选中的最短区间长度”,由此可以想到先给区间按照长度排序(和别的按照左右端点排序的题不一样)。然后考虑双指针法,右端点按照长度从小到大遍历,并把当前线段覆盖的区间整体+1,然后内循环只要有一个点的覆盖次数大于等于m,左指针就不断朝右指针移动更新答案,同时把指向的区间整体-1。可以用线段树来维护覆盖次数,需要维护的就是区间点值的最大值(查询的时候直接查询树根的值即可),看到数据范围记得离散化。

证明:当右指针指向某一个区间时,左指针不断移动删除的区间必定不可能对答案有贡献(因为删掉以后仍然有点的被覆盖次数大于等于m,此时被删区间的下一个区间一定更优,这是由花费的计算方法决定的)。

可能有人会问,这样得到的答案区间一定是从小到大排序后一段连续的区间吗(此区间是排序后线段下标构成的区间)?其实不一定,比如m = 3,而线段为(1, 2), (1, 3), (5, 6), (1, 10)。但其实我们并不关心这个,排序只是为了方便计算,最终得到的就是一个最大减最小值而已。

#include <bits/stdc++.h>
using namespace std;
int n, m;
struct Seg {
	int l, r, len;
} seg[500005];
bool cmp(Seg a, Seg b) {
	if(a.len != b.len) return a.len < b.len;
	else return a.l < b.l;
}
struct SegmentTree {
	int l, r, dat, mx, add;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define add(x) tree[x].add
	#define mx(x) tree[x].mx
} tree[8000005];
void build(int p, int l, int r) {
	l(p) = l, r(p) = r;
	if(l == r) {
		mx(p) = 0;
		return;
	}
	int mid = (l + r) >> 1;
	build(2 * p, l, mid);
	build(2 * p + 1, mid + 1, r);
	mx(p) = 0;
}
void spread(int p) {
	if(add(p)) {
		add(2 * p) += add(p);
		add(2 * p + 1) += add(p);
		mx(2 * p) += add(p);
		mx(2 * p + 1) += add(p);
		add(p) = 0;
	}
}
void change(int p, int l, int r, int d) {
	//cout << p << endl;
	if(l <= l(p) && r >= r(p)) {
		add(p) += d;
		mx(p) += d;
		return;
	}
	spread(p);
	int mid  = (l(p) + r(p)) / 2;
	if(l <= mid) change(2 * p, l, r, d);
	if(r > mid) change(2 * p + 1, l, r, d);
	mx(p) = max(mx(2 * p), mx(2 * p + 1));
}
int main() {
	cin >> n >> m;
	int v[1000005], cnt = 0;
	for(int i = 1; i <= n; i++) {
		cin >> seg[i].l >> seg[i].r;
		seg[i].len = seg[i].r - seg[i].l;
		v[++cnt] = seg[i].r;
		v[++cnt] = seg[i].l;
 	}
 	sort(seg + 1, seg + n + 1, cmp);
 	sort(v + 1, v + cnt + 1);
 	int len = unique(v + 1, v + cnt + 1) - v - 1;

 	for(int i = 1; i <= n; i++) {
 		seg[i].l = lower_bound(v + 1, v + len + 1, seg[i].l) - v;
 		seg[i].r = lower_bound(v + 1, v + len + 1, seg[i].r) - v;
 	}
 	int pos = 1, ans = 0x3f3f3f3f;
 	build(1, 1, 1000005);//别忘记建树  由于500000个线段,而线段是两个端点 因此要开1000000
 	for(int i = 1; i <= n; i++) {
 		change(1, seg[i].l, seg[i].r, 1);
 		while(tree[1].mx >= m && pos <= i) {
 			ans = min(ans, seg[i].len - seg[pos].len);
 			change(1, seg[pos].l, seg[pos].r, -1);
 			pos++;
 		}
 	}
 	if(ans != 0x3f3f3f3f) cout << ans;
 	else cout << -1;
	return 0;
}
posted @ 2021-04-15 10:40  脂环  阅读(59)  评论(0编辑  收藏  举报