天天看点

题解 DTOJ #2802. 区间(interval)

欢迎访问 My Luogu Space。

【题目大意】

在数轴上有 n n n 个区间,请选出最少 m m m 个区间,使得这 m m m 个区间至少共同包含一个位置。

选取的花费是选出的区间中最长的区间的长度减去最短的区间的长度。请求出最小花费。如果无法选出则输出“-1”。

【题解】

线段树

由于我们的花费是最长和最短的区间的长度的差值,具有单调性,因此我们可以将区间的长度排序,用线段树维护被最多区间覆盖的点的覆盖数。

我们将区间从小到大放入线段树,直到线段树根节点的值等于 m m m,说明有某一个点被 m m m 个区间覆盖了,此时统计答案,然后将最早加入的区间删去,再统计答案然后删去,继续直到线段树根的值小于 m m m。由于取到了大于 m m m 个区间没有意义,这样统计出来的答案就会最优。

最后,区间位置需要离散化。离散化用unique,不要用map,不然会很慢。

【代码】

// output format !!!
// long long !!!
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN = 500000+10;
struct DATA{
	int l, r, len;
	bool operator<(DATA a)const{
		return len < a.len;
	}
}s[MAXN];

int n, m, ct;
int t[MAXN*8], laz[MAXN*8];
vector<int> C;

#define ls (x<<1)
#define rs (x<<1|1)
void spread(int x, int l, int r){
	int mid = (l+r)>>1;
	t[ls] += laz[x];
	t[rs] += laz[x];
	laz[ls] += laz[x];
	laz[rs] += laz[x];
	laz[x] = 0;
}
void modify(int x, int l, int r, int ql, int qr, int v){
	if(ql<=l && r<=qr){
		t[x] += v;
		laz[x] += v;
		return;
	}
	if(laz[x]) spread(x, l, r);
	int mid = (l+r)>>1;
	if(ql <= mid) modify(ls, l, mid, ql, qr, v);
	if(qr > mid) modify(rs, mid+1, r, ql, qr, v);
	t[x] = max(t[ls], t[rs]);
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; ++i){
		scanf("%d%d", &s[i].l, &s[i].r);
		s[i].len = s[i].r-s[i].l;
		C.push_back(s[i].l);
		C.push_back(s[i].r);
	}
	sort(C.begin(), C.end());
	for(int i=1; i<=n; ++i){
		s[i].l = lower_bound(C.begin(), C.end(), s[i].l)-C.begin()+1;
		s[i].r = lower_bound(C.begin(), C.end(), s[i].r)-C.begin()+1;
	}
	sort(s+1, s+n+1);
	int L=1, R=0, ans=1e9;
	while(R < n){
		while(t[1]<m && R<n) ++R, modify(1, 1, n+n, s[R].l, s[R].r, 1);
		if(t[1]<m && R==n) break;
		while(t[1]>=m && L<=n){
			ans = min(ans, s[R].len-s[L].len);
			modify(1, 1, n+n, s[L].l, s[L].r, -1);
			++L;
		}
	}
	printf("%d", ans==1e9?-1:ans);
	return 0;
}