title
Description
在数轴上有 \(N\) 个闭区间 \([l_1,r_1],[l_2,r_2],...,[l_n,r_n]\)。现在要从中选出 \(M\) 个区间,使得这 \(M\) 个区间共同包含至少一个位置。换句话说,就是使得存在一个 \(x\),使得对于每一个被选中的区间 \([l_i,r_i]\),都有 \(l_i\leqslant x\leqslant r_i\)。
对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。区间 \([l_i,r_i]\) 的长度定义为 \(r_i−l_i\),即等于它的右端点的值减去左端点的值。
求所有合法方案中最小的花费。如果不存在合法的方案,输出 \(−1\) 。
Input
第一行包含两个正整数 \(N,M\) 用空格隔开,意义如上文所述。保证 \(1\leqslant M\leqslant N\)
接下来 \(N\) 行,每行表示一个区间,包含用空格隔开的两个整数 \(l_i\) 和 \(r_i\) 为该区间的左右端点。
\(N\leqslant 500000,M\leqslant 200000,0\leqslant l_i\leqslant r_i\leqslant 10^9\)
Output
只有一行,包含一个正整数,即最小花费。
Sample Input
6 3
3 5
1 2
3 4
2 2
1 5
1 4
Sample Output
2
analysis
有一点再次碰上网络流24题的感觉,但是细看一下,根本不一样(略过这段话)。
又来堂而皇之地考虑暴力怎么写了,肯定要把所有区间排序一下,然后枚举一个区间作为左端点,一个区间作为右端点,那么答案就是在这个大区间中来选的。
这里有一个思维转化:
有 \(M\) 个区间共同包含一个位置,就相当于这一段区间内有被经过 \(M\) 次的点。
所以做法就出来了,我们把这一段的所有区间的对应的一段的经过次数都加一,最后只需查看一下这一段中是否出现了一个被经过 \(M\) 次的点,一旦存在就说明,我们一定可以找到其中的 \(M\) 个区间满足题目的要求,就可以用右端点的那个区间长度-左端点的那个区间长度来更新答案。
然后用线段树来维护,复杂度是 \(O(N^2\log N)\) 。
不难(很难)发现:右端点其实不单调不降的,所以没有必要枚举右端点了,只需要用一个单调指针一直往后扫就可以了,复杂度就变成了 \(O(N\log N)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10,inf=0x7fffffff;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++='\n';
}
int tree[maxn<<2],atag[maxn<<2];//由于一个区间有两个点,所以最坏情况要开八倍数组,maxn已经乘2了
inline void pushdown(int now,int l,int r)
{
if (l==r) { atag[now]=0; return ; }
atag[now<<1]+=atag[now], atag[now<<1|1]+=atag[now];
tree[now<<1]+=atag[now], tree[now<<1|1]+=atag[now];
atag[now]=0;
}
inline void Change(int now,int l,int r,int tl,int tr,int k)
{
if (tl<=l && r<=tr)
{
tree[now]+=k, atag[now]+=k;
return ;
}
if (atag[now]) pushdown(now,l,r);
int mid=(l+r)>>1;
if (tl<=mid) Change(now<<1,l,mid,tl,tr,k);
if (tr>mid) Change(now<<1|1,mid+1,r,tl,tr,k);
tree[now]=max(tree[now<<1],tree[now<<1|1]);
}
struct Orz{int l,r,i;}q[maxn];
inline bool cmp(Orz a,Orz b)
{
return a.i<b.i;
}
int c[maxn<<1],cnt;
int main()
{
int n,m;read(n);read(m);
for (int i=1; i<=n; ++i)
{
read(q[i].l),read(q[i].r);
q[i].i=q[i].r-q[i].l;
c[++cnt]=q[i].l,c[++cnt]=q[i].r;
}
sort(c+1,c+cnt+1);
cnt=unique(c+1,c+cnt+1)-c-1;
for (int i=1; i<=n; ++i) q[i].l=lower_bound(c+1,c+cnt+1,q[i].l)-c,q[i].r=lower_bound(c+1,c+cnt+1,q[i].r)-c;//找到区间i可修改的范围
sort(q+1,q+n+1,cmp);
int now=0,ans=inf;
for (int i=1; i<=n; ++i)
{
while (tree[1]<m && now<n) ++now,Change(1,1,cnt,q[now].l,q[now].r,1);
if (tree[1]==m) ans=min(ans,q[now].i-q[i].i);
Change(1,1,cnt,q[i].l,q[i].r,-1);
}
write(ans==inf?-1:ans);
flush();
return 0;
}