题解:P6044 [POI2018] Prawnicy

题目传送门


简要题意

定义一个区间 \((l,r)\) 的长度为 \(r-l\) ,空区间的长度为 \(0\)

给定数轴上 \(n\) 个区间,请选择其中恰好 \(k\) 个区间,使得交集的长度最大。

(摘自 U252799 [POI2018]Prawnicy

解题思路

这道题是一个很明显的贪心。显然,合法区间肯定是从一个端点开始到一个端点结束。证明:

因为如果它不是从端点开始结束必定可以延伸到一条线段的端点,如图:

所以我们只需要从小到大枚举左端点,找到右端点求差值就行了。

怎么枚举呢?我们可以那一个堆或者优先队列维护一个关于线段的三元组,左端点为第一项,右端点为第二项,序号最后一项(没有序号线段放入队列是顺序会变,不方便输出)。然后把线段扔入,计算合法法的最大差值,大小为超过 \(K\) 时,把左端点最小的出堆。为什么是最小的呢?证明:

我们可以分类讨论

  • 线段的右端点不是最大
    在这种情况下,由于还有其他线段的右端点比它大,它无法做出更大的贡献,所以加上不会导致结果更优。

  • 线段的右端点是最大的
    在这种情况下,其他的线段一定包含在它中(因为它左端点最小右端点最大),长度取决于其它线段,所以加上不会导致结果更优。

在比较差值时,要记录起点和终点,方便输出选取的是那几条线段。

AC代码

#include<bits/stdc++.h>
using namespace std;

int n,k;
const int N=1e6+5;
struct node{
	int beginn,endd,id;
	//左右端点和 ID 。
}a[N];
void in(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		cin>>a[i].beginn>>a[i].endd,a[i].id=i;
}//输入线段,由于顺序会改变所以记录ID。

bool cmp(node x,node y){
	if(x.beginn!=y.beginn)
		return x.beginn<y.beginn;
	return x.endd<y.endd;
}//排序,左端点小的在前。相同时右端点小的在前。
priority_queue<int,vector<int>,greater<int> > q;
//建立小根堆。
int timen=INT_MIN,start=-1,finish=-2;
void work(){
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		q.push(a[i].endd);
		//加入线段
		if(q.size()>k)	q.pop();
		//维持大小为 K 。
		if(q.size()==k)
			if(timen<q.top()-a[i].beginn){
				timen=q.top()-a[i].beginn;
				//求最大差值。
				start=a[i].beginn;	finish=q.top();
				//记录起点、终点。
			}
	}
}

void out(){
	cout<<timen<<'\n';
	int t=k;
	for(int i=1;i<=n&&t;i++)
		if(a[i].beginn<=start&&a[i].endd>=finish)
			cout<<a[i].id<<' ',t--;
			//输出符合条件的线段
}

signed main(){
	in();
	work();
	out();
	return 0;
	//结束
}
posted @ 2025-04-18 14:32  _Charllote  阅读(30)  评论(0)    收藏  举报