P1712 [NOI2016] 区间

[NOI2016] 区间

题目描述

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

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

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

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

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

对于全部的测试点,保证 1 ≤ m ≤ n 1 \leq m \leq n 1mn 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq 5 \times 10^5 1n5×105 1 ≤ m ≤ 2 × 1 0 5 1 \leq m \leq 2 \times 10^5 1m2×105 0 ≤ l i ≤ r i ≤ 1 0 9 0 \leq l_i \leq r_i \leq 10^9 0liri109



解析:

将所有区间按长度排序,则最优解一定是连续的几个区间

如果区间 i i i 是最短的区间,设 R i R_i Ri 是满足条件的区间中最短的那个,此时答案为 l e n ( R i ) − l e n ( i ) len(R_i)-len(i) len(Ri)len(i)

可以发现, R i R_i Ri 单调不减,可以用双指针

所以依次选择排序后的区间,如果满足某个点覆盖次数大于等于m,则将最早加入的区间删除,直到小于m

用线段树维护区间中点覆盖次数的最大值,需要离散化

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
const int maxn = 1e6+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;

struct sgt{
	int v, tag;
}t[maxn << 2];
struct node{
	int l, r;
	int len;
	bool operator < (const node &b) const{
		return len < b.len;
	}
}a[maxn];

int res = INF;
int x[maxn<<1], tot;
int n, m;

int ls(int x){return x << 1;}
int rs(int x){return x << 1 | 1;}

void pushup(int k){
	t[k].v = max(t[ls(k)].v, t[rs(k)].v);
}
void build(int k, int l, int r){
	t[k].tag = 0;
	if(l == r){
		t[k].v = 0;
		return;
	}
	int mid = (l+r) >> 1;
	build(ls(k), l, mid);
	build(rs(k), mid+1, r);
	pushup(k);
}
void pushdown(int k){
	if(t[k].tag){
		t[ls(k)].v += t[k].tag;
		t[ls(k)].tag += t[k].tag;
		t[rs(k)].v += t[k].tag;
		t[rs(k)].tag += t[k].tag;
		
		t[k].tag = 0;
	}
}
void modify(int k, int l, int r, int x, int y, int w){
	if(x <= l && y >= r){
		t[k].tag += w;
		t[k].v += w;
		return;
	}
	int mid = (l+r) >> 1;
	pushdown(k);
	
	if(x <= mid)
		modify(ls(k), l, mid, x, y, w);
	if(y > mid)
		modify(rs(k), mid+1, r, x, y, w);
	pushup(k);
}
int query(int k, int l, int r, int x, int y){
	if(x <= l && y >= r)
		return t[k].v;
	pushdown(k);
	int mid = (l+r) >> 1;
	int res = 0;
	if(x <= mid)
		res = max(res, query(ls(k), l, mid, x, y));
	if(y > mid)
		res = max(res, query(rs(k), mid+1, r, x, y));
	return res;
}


int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		cin >> a[i].l >> a[i].r;
		a[i].len = (a[i].r - a[i].l);
		x[++tot] = a[i].l;
		x[++tot] = a[i].r;
	}
	
	sort(x+1, x+tot+1);
	tot = unique(x+1, x+tot+1) - (x+1);
	sort(a+1, a+1+n);
	
	for(int i = 1; i <= n; i++){
		a[i].l = lower_bound(x+1, x+tot+1, a[i].l) - x;
		a[i].r = lower_bound(x+1, x+tot+1, a[i].r) - x;
	}
	
	build(1, 1, tot);
	int pos = 1;
	for(int i = 1; i <= n; i++){
		modify(1, 1, tot, a[i].l, a[i].r, 1);
		while(query(1, 1, tot, 1, tot) >= m){
			res = min(res, a[i].len - a[pos].len);
			modify(1, 1, tot, a[pos].l, a[pos].r, -1);
			pos++;
		}
	}
	
	if(res == INF)
		cout << -1 << endl;
	else
		cout << res << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值