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; }
看都看了,顺手点个推荐呗 :)