CF1884C Medium Design 题解
一道不错的用线段树考虑贡献的题。
这里提供一种和官方题解思路不太一样的方法。
简要题意
给定
思路
如果操作后最大值和最小值的位置确定,考虑每个区间,发现只有仅覆盖了最大值的区间会对答案产生贡献,其他区间(即覆盖了最小值的区间)则不用考虑,故可以反过来枚举所有最小值可能在的位置,对于每一个最小值的位置,可能的答案就是所有未覆盖该位置的区间进行区间加
显然这是个 RMQ 问题,用线段树维护。但每次枚举最小值直接暴力区间修改不覆盖最小值的区间复杂度无法接受,所以可以换一下思路,先把所有区间都覆盖一遍,然后从左到右枚举最小值的时候考虑每个区间的贡献:枚举到点
还需要注意的一点,由于区间范围很大,需要离散化,离散化之后会使原本不相邻的两个区间变成相邻的,这会漏掉一些情况,当然可以人为在离散化对不相邻的区间之间增加一个位置,但实现起来稍微有点麻烦,而可以发现这道题如果存在不相邻的区间,就意味着序列的最小值一定是
Code
#include<bits/stdc++.h>
using namespace std;
const int N=500010;
struct Seg{int l,r;}seg[N];
bool cmp(Seg a,Seg b){return a.l==b.l?a.r<b.r:a.l<b.l;}
struct Node{int ma,tag;}tr[N<<2];
vector<int> dl[N],dr[N];//分别存左、右端点在 i 的区间编号
inline void pushup(int p){tr[p].ma=max(tr[p<<1].ma,tr[p<<1|1].ma);}
inline void addtag(int p,int d){tr[p].tag+=d,tr[p].ma+=d;}
inline void pushdown(int p)
{
if(tr[p].tag)
{
addtag(p<<1,tr[p].tag),addtag(p<<1|1,tr[p].tag);
tr[p].tag=0;
}
}
void build(int p,int pl,int pr)//只是方便对整棵树清零
{
tr[p].ma=tr[p].tag=0;
if(pl==pr) return;
int mid=pl+pr>>1;
build(p<<1,pl,mid),build(p<<1|1,mid+1,pr);
}
void update(int p,int pl,int pr,int L,int R,int d)
{
if(L<=pl&&pr<=R)
{
addtag(p,d);
return;
}
pushdown(p);
int mid=pl+pr>>1;
if(mid>=L) update(p<<1,pl,mid,L,R,d);
if(mid<R) update(p<<1|1,mid+1,pr,L,R,d);
pushup(p);
}
int t,m,n,a[N];
int main()
{
scanf("%d",&t);
while(t--)
{
int res=0;
bool k=0,ll=1,rr=1;//k:是否存在不连续的区间,ll:最左边没有被覆盖,rr:最右边没有被覆盖
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d",&seg[i].l,&seg[i].r),a[(i-1)*2+1]=seg[i].l,a[(i-1)*2+2]=seg[i].r,rr&=(seg[i].r<m),ll&=(seg[i].l>1);
k=(ll||rr);//只要两边有空着的就计入特判的情况
sort(seg+1,seg+1+n,cmp);//对区间排序,方便判断是否存在没有被任何一个区间覆盖的位置
int rmax=1;
for(int i=1;i<=n;++i)
{
if(seg[i].l>rmax+1) k=1;//左边的区间最右的端点也与目前左端点间隔了距离 ,即找到了一个没有被任何区间覆盖的位置
while(seg[i].l==seg[i+1].l) ++i;//左端点相同的区间找到右端点最右的一个(已经按右端点为第二关键字排序)
rmax=max(rmax,seg[i].r);//更新左边区间最右端点的位置,注意一定要取max,因为左端点更靠右的区间右端点可能反而小
}
//离散化
sort(a+1,a+1+2*n);
int cnt=unique(a+1,a+1+2*n)-(a+1);
//多测清空
build(1,1,cnt);
for(int i=1;i<=cnt;++i) dl[i].clear(),dr[i].clear();
//离散化后重新赋值,顺便把所有区间进行覆盖,和记录左右端点位置对应的区间编号
for(int i=1;i<=n;++i)
seg[i].l=lower_bound(a+1,a+1+cnt,seg[i].l)-a,seg[i].r=lower_bound(a+1,a+1+cnt,seg[i].r)-a,
update(1,1,cnt,seg[i].l,seg[i].r,1),dl[seg[i].l].push_back(i),dr[seg[i].r].push_back(i);
if(k){printf("%d\n",tr[1].ma);continue;}//特判存在没有被任何区间覆盖的位置的情况
for(int i=1;i<=cnt;++i)
{
for(auto j:dl[i]) update(1,1,cnt,seg[j].l,seg[j].r,-1);//消除覆盖了这个位置的区间的影响
res=max(res,tr[1].ma);//更新最大的答案
for(auto j:dr[i]) update(1,1,cnt,seg[j].l,seg[j].r,1);//加上没有覆盖这个位置的区间的影响,注意该操作要在更新答案之后
}
printf("%d\n",res);
}
return 0;
}
一个朋友的另一种做法