CF1555E Boring Segments

题解

第一眼想到了二分,然而因为最大最小值都不确定所以做不了。

因为两端都不确定,考虑先固定左端点(即最小值),不断将右端点右移,直到有解。

有解后再将左端点右移,此时有解时的右端点一定在原本的右端点处或右端点的右边(满足单调性)

这样右端点只会右移,在指针移动这一方面可以做到O(n)

貌似双指针是求最小极差的套路。

考虑如何快速判定一个权值区间是否有解。

先将序列排序,然后用线段树维护线段的增加与删除,这就是一个线段树维护区间覆盖的经典问题了。

具体而言,每个节点维护一个tot[i],表示完全覆盖该区间的线段数量

再维护一个cover[i],表示该区间是否被完全覆盖(有可能是tot[i]中的线段一条覆盖的,也可能是多条线段拼起来)

通过cover[i]=(tot[i]>0)||(cover[lc]&cover[rc])(lc,rc分别是左右儿子)维护。

注意当tot清零时要更新cover[i]=cover[lc]&cover[rc]

代码

#include<bits/stdc++.h>
using namespace std;
#define N 4000010
struct line
{
	int l,r,w;
}lines[N];
bool operator <(line a,line b)
{
	return a.w<b.w;
}
int tot[N];
bool cover[N];
#define lc id*2
#define rc id*2+1
#define mid (l+r)/2
void modify(int id,int l,int r,int tl,int tr,int val)
{
	//cout<<id<<" "<<l<<" "<<r<<" "<<tl<<" "<<tr<<endl;
	if(tl<=l&&r<=tr)
	{
		tot[id]+=val;
		if(!tot[id]) cover[id]=cover[lc]&cover[rc];
		else cover[id]=true;
		//printf("[%d %d] %d\n",l,r,cover[id]);
		return;
	}
	if(tl<=mid) modify(lc,l,mid,tl,tr,val);
	if(tr>mid) modify(rc,mid+1,r,tl,tr,val);
	cover[id]=(tot[id]>0)||(cover[lc]&cover[rc]);
	//printf("[%d %d] %d\n",l,r,cover[id]);
}
signed main()
{
	int n,m,cnt=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int l,r,w;
		scanf("%d%d%d",&l,&r,&w);
		r--;
		if(r<l) continue;
		if(r>m-1) r=m-1;
		lines[++cnt]=(line){l,r,w};
	}
	sort(lines+1,lines+1+cnt);
	int l=1,r=1,ans=1e9;
	m--;
	while(r<=cnt)
	{
		while(r<=cnt)
		{
			//printf("add [%d %d]\n",lines[r].l,lines[r].r);
			modify(1,1,m,lines[r].l,lines[r].r,1);
			if(cover[1]) break;
			r++;
		}
		//printf("erase [%d %d]\n",lines[l].l,lines[l].r);
		modify(1,1,m,lines[l].l,lines[l].r,-1);
		if(r<=cnt) ans=min(ans,lines[r].w-lines[l].w);
		else break;
		l++;
		if(r<l) r++;
	}
	cout<<ans;
}

  

posted @ 2021-08-18 21:30  linzhuohang  阅读(44)  评论(0编辑  收藏  举报