luogu1712 区间 题解

分析

既然要最小化选出的最长区间长度减去最短区间长度,那么很容易想到一个典型的双指针算法:

将区间长度递增排序,维护指针 \(l\)\(r\),表示选择 \([l,r]\) 中所有的线段。

依次选择每条线段(也就是 \(r\) 在递增),在满足有一个点被覆盖 \(m\) 次的条件下,尽可能将 \(l\) 提前并删去对应的线段。答案就是 \(\min{ \{len_r-len_l \} }\)

所以离散化,在离散化后的值域上建一棵线段树,维护区间内的点被覆盖的最多次数。

那么只要根节点的值大于等于 \(m\),就是合法的选取方案。

CODE

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n, m, cnt, c[N<<1];
struct L { int l, r, len; } a[N];
bool operator<(L a,L b) { return a.len<b.len; }
struct Segment_Tree {
	int l, r, w, tag;
	#define l(u) t[u].l
	#define r(u) t[u].r
	#define w(u) t[u].w
	#define tag(u) t[u].tag
} t[N<<2];
void maketag(int u,int val) { tag(u)+=val, w(u)+=val; }
void pushup(int u) { w(u)=max(w(u<<1),w(u<<1|1)); }
void pushdown(int u) {
	if(tag(u)) {
		maketag(u<<1,tag(u));
		maketag(u<<1|1,tag(u));
		tag(u)=0;
	}
}
void build(int u,int l,int r) {
	l(u)=l, r(u)=r;
	if(l==r) { w(u)=tag(u)=0; return; }
	int mid=(l+r)/2;
	build(u<<1,l,mid), build(u<<1|1,mid+1,r);
	pushup(u);
}
void modify(int u,int l,int r,int val) {
	if(l<=l(u)&&r(u)<=r) maketag(u,val);
	else if(!(r(u)<l||r<l(u))) {
		pushdown(u);
        int mid=(l(u)+r(u))/2;
		if(l<=mid) modify(u<<1,l,r,val);
        if(r>mid) modify(u<<1|1,l,r,val);
		pushup(u);
	}
}
int query() { return w(1); }
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		int l, r; scanf("%d%d",&l,&r);
		a[i].l=l, a[i].r=r, a[i].len=r-l;
		c[++cnt]=l, c[++cnt]=r;
	}
    sort(a+1,a+n+1);
	sort(c+1,c+cnt+1);
	cnt=unique(c+1,c+cnt+1)-c-1;
	for(int i=1;i<=n;++i) {
		a[i].l=lower_bound(c+1,c+cnt+1,a[i].l)-c;
		a[i].r=lower_bound(c+1,c+cnt+1,a[i].r)-c;
	}
    // 离散化
	int l=1, ans=(1<<30);
	build(1,1,cnt);
	for(int i=1;i<=n;++i) {
        // r是递增的,直接用i代替
		modify(1,a[i].l,a[i].r,1);
		while(query()>=m) {
			ans=min(ans,a[i].len-a[l].len);
			modify(1,a[l].l,a[l].r,-1);
			++l;
		}
	}
	printf("%d\n",ans!=(1<<30)? ans:-1);
}

luogu1712 区间 题解
https://yozora0908.github.io/2022/lg1712-solution/
作者
yozora0908
发布于
2022年5月28日
许可协议