【bzoj4653】[Noi2016]区间 双指针法+线段树

题目描述

在数轴上有 n个闭区间 [l1,r1],[l2,r2],...,[ln,rn]。现在要从中选出 m 个区间,使得这 m个区间共同包含至少一个位置。换句话说,就是使得存在一个 x,使得对于每一个被选中的区间 [li,ri],都有 li≤x≤ri。
对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。区间 [li,ri] 的长度定义为 ri−li,即等于它的右端点的值减去左端点的值。
求所有合法方案中最小的花费。如果不存在合法的方案,输出 −1。

输入

第一行包含两个正整数 n,m用空格隔开,意义如上文所述。保证 1≤m≤n
接下来 n行,每行表示一个区间,包含用空格隔开的两个整数 li 和 ri 为该区间的左右端点。
N<=500000,M<=200000,0≤li≤ri≤10^9

输出

只有一行,包含一个正整数,即最小花费。

样例输入

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

样例输出

2


题解

双指针法+线段树

这道傻*题我一开始竟然没看出来 = =

由于代价只跟最短和最长的区间长度有关,和区间个数无关,因此如果我们确定了一个最短的区间,那么可以依次寻找长度比它大的区间,直到找到答案或无论如何也找不到答案。

那么我们就可以将区间按照长度排序,并将区间端点离散化,枚举最短的区间使用线段树维护区间内被覆盖次数最多的点的被覆盖次数,直接维护区间最大值、区间修改就好了。

然后我们可以发现,随着最短区间的变化(即头指针的移动),答案最长区间(即尾指针)是单调不减的,所以可以使用双指针法来维护它。

时间复杂度$O(nlog n)$

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 500010
#define lson l , mid , x << 1
#define rson mid + 1 , r , x << 1 | 1
using namespace std;
struct data
{
	int l , r , len;
}a[N];
int v[N << 1] , maxn[N << 3] , add[N << 3];
bool cmp(data a , data b)
{
	return a.len < b.len;
}
void pushup(int x)
{
	maxn[x] = max(maxn[x << 1] , maxn[x << 1 | 1]);
}
void pushdown(int x)
{
	if(add[x])
	{
		maxn[x << 1] += add[x] , maxn[x << 1 | 1] += add[x];
		add[x << 1] += add[x] , add[x << 1 | 1] += add[x];
		add[x] = 0;
	}
}
void update(int b , int e , int a , int l , int r , int x)
{
	if(b <= l && r <= e)
	{
		maxn[x] += a , add[x] += a;
		return;
	}
	pushdown(x);
	int mid = (l + r) >> 1;
	if(b <= mid) update(b , e , a , lson);
	if(e > mid) update(b , e , a , rson);
	pushup(x);
}
int main()
{
	int n , m , i , p = 0 , ans = 1 << 30;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= n ; i ++ ) scanf("%d%d" , &a[i].l , &a[i].r) , a[i].len = a[i].r - a[i].l , v[i] = a[i].l , v[i + n] = a[i].r;
	sort(a + 1 , a + n + 1 , cmp) , sort(v + 1 , v + 2 * n + 1);
	for(i = 1 ; i <= n ; i ++ ) a[i].l = lower_bound(v + 1 , v + 2 * n + 1 , a[i].l) - v , a[i].r = lower_bound(v + 1 , v + 2 * n + 1 , a[i].r) - v;
	for(i = 1 ; i <= n ; i ++ )
	{
		while(p < n && maxn[1] < m) p ++ , update(a[p].l , a[p].r , 1 , 1 , 2 * n , 1);
		if(maxn[1] < m) break;
		ans = min(ans , a[p].len - a[i].len);
		update(a[i].l , a[i].r , -1 , 1 , 2 * n , 1);
	}
	printf("%d
" , ans == 1 << 30 ? -1 : ans);
	return 0;
}

 

原文地址:https://www.cnblogs.com/GXZlegend/p/7128279.html