P1712 [NOI2016] 区间(线段树 + 贪心 + 双指针)

文章讨论了一个关于区间选择的问题,目标是在给定n个闭区间中选择m个,使得这些区间共同包含至少一个位置,最小化花费(最长区间长度减去最短区间长度)。通过线段树和双指针策略优化了暴力法的时间复杂度,达到O(nlogn)。
摘要由CSDN通过智能技术生成

题目描述

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

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

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

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

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

输入格式

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

第 2 到第(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 解释

解析:

从题目中我们可以观察到,选着线段和 顺序无关,我们可以进行排序。

暴力做法就是 :

我们对线段的长度进行从小到大排序;

当遍历到有点满足x == m时 ,当前 j  减去 i ;

时间复杂度为O(n^2); 爆

我们对区间搜索进行,我们可以用线段树记录区间的 x 进行优化,离散化一下 防止无用的点 :

在利用区间 的 查询时,我们要记录其区间 的中的个数。

在利用双指针进行搜索:

i  , j 含义 i 代表:当在i点如果 满足其 m个时,我们i++。

while(t[1].cnt == m && i <= j)
        {
            i++;
            change(1,s[i].l,s[i].r,-1);    
        } 
        ans = min(s[j].len - s[i].len,ans);

这里要注意 i++ 是,当前满足条件的 点 ,下一个点 就不满足了。

因为我们是 线段长度从小到大 遍历。

所以i 和 j越靠近越小。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ls u<<1
#define rs u<<1|1
const int N = 500005;
struct line{
	int l,r,len;
	bool operator < (line& b)
	{
		return len < b.len;
	}
}s[N];
struct node{
	int l,r,cnt,lazy;
}t[N*2*4];
void pushup(int u)
{
	t[u].cnt = max(t[ls].cnt ,t[rs].cnt);
} 
void build(int u,int l,int r)
{
	t[u].l = l,t[u].r = r;
	t[u].cnt = 0;
	if(l == r){
		return;
	}	
	int mid = l + r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(u);
} 
void downup(int u){
	if(t[u].lazy){
		t[ls].lazy += t[u].lazy;
		t[rs].lazy += t[u].lazy;
		t[ls].cnt += t[u].lazy;
		t[rs].cnt += t[u].lazy;
		t[u].lazy = 0;
	}
}

void change(int u,int l,int r,int v)
{
	if(t[u].l > r || t[u].r < l){
		return;
	}
	if(t[u].l >= l&& t[u].r <= r)
	{
		t[u].lazy += v;
		t[u].cnt += v;
		return;
	}
	downup(u);
	change(ls,l,r,v);
	change(rs,l,r,v);
	pushup(u);//这个没有加 
}

int main()
{
	int n,m;
	cin >> n >> m;
	vector<int> v(n*2);
	int j = 0;
	for(int i =1;i <= n;i++)
	{
		cin >> s[i].l >> s[i].r;
		s[i].len = s[i].r - s[i].l+1;
		v[j++] = s[i].l;
		v[j++] = s[i].r;
	}
	sort(v.begin(),v.end());
	int k = unique(v.begin(),v.end()) - v.begin();
	sort(s+1,s+1+n);
	for(int i = 1;i <= n;i++)
	{
		s[i].l = lower_bound(v.begin(),v.begin()+k,s[i].l) - v.begin();
		s[i].r = lower_bound(v.begin(),v.begin()+k,s[i].r) - v.begin();
	}
	
	build(1,1,k);
	int ans = 1e9;
	for(int i = 0,j = 0;j <= n;)
	{
		while(t[1].cnt < m && j <= n)
		{
			j++;
			change(1,s[j].l,s[j].r,1); 
		}
		if(t[1].cnt < m) break;
		while(t[1].cnt == m && i <= j)
		{
			i++;
			change(1,s[i].l,s[i].r,-1);	
		} 
		ans = min(s[j].len - s[i].len,ans);
	}
	cout <<(ans == 1e9 ? -1 : ans);
	return 0;
}

时间复杂度:O(n*logn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值