题解: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;
//结束
}



浙公网安备 33010602011771号