题目链接:点击查看
题目大意:给出n个线段代表集合,现在问若可以将其中任意一个线段删除,则能够形成最多多少个独立的集合(取并集后)
题目分析:看到区间不难想到线段树了,虽然这个题也可以用stl贪心做,但需要考虑的因素太多了,还是用线段树直接莽吧,虽然前期的理论已经准备的很充分了,但在中途写线段树的时候还是写崩了,最开始的那个线段树没有想太多,直接更新到叶子结点,TLE,加个lazy优化一下吧,但优化的不到位,TLE,最后还是参考了zx学长的代码,豁然开朗,想明白了对于这个题目的几个关键的点,冲着这几个点改了一下线段树的内部实现,然后就A了
上面说的是这个题的心路历程,回到这个题目来,最直接的想法就是用线段树维护一个cover变量,表示每个区间被覆盖的次数,再单独维护一个sum变量,用来表示区间内有多少个独立的集合,也就是不接壤的线段,最后就是维护一个布尔类型的ll和rr,分别表示左端点和右端点是否被覆盖,在区间合并的时候要用到
具体该如何使用上面的变量呢,首先将n个线段读入到线段树中,此后以此枚举每个线段删除之后的答案,根据上面变量的定义,显然答案就是tree[1].sum,所以对于更新来说我们只需要让当前枚举的线段的cover减一即可,最后别忘了回溯,时间复杂度是nlogn级别的,带点常数无伤大雅
这个题的关键就是区间的范围给的太大了,有2e9那么多,只能离散化,但面临的一个问题是,离散化后只剩下了端点的情况,端点内部的区间都被离散化掉了,但在这个题目中我们是需要用到的,所以在离散化的时候将区间内、区间左、区间右这三个位置顺便一起离散化一下就可以了,相当于将区间内的所有空白点压缩成了一个点,具体实现就是让端点都乘以二,这样既能解决题目中相邻区间不相交的问题,如[1,4][5,6]就属于两个集合,而不是一个集合,也能解决上述离散化的问题,一举两得
讲完离散化后我们就可以直接写线段树了,具体实现还是看代码吧,打上注释了都,只可意会不可言谈
最后说一下这个题的几个关键点吧,因为我们最终需要的是tree[1].sum,换句话说,只有涉及到最终答案的值才需要更新一下,假如某个区间已经被更新了,那么其接下来的子树就无需遍历了,因为知道了当前区间的状态,就足以向上维护最终答案了,而对于lazy变量,在这个题目中实际上就是cover变量,因为这个题目无需向下传递任何信息,只需要向上传递信息用来维护最终答案,所以在这个题目中的cover变量的用途具体也是用来维护状态的
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<sstream>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int N=2e5+100;
struct Node
{
int l,r,sum,cover;//sum:有多少个区间段 cover:被覆盖了几次
bool ll,rr;//左右端点是否被覆盖
}tree[N<<5];
void pushup(int k)
{
if(tree[k].cover)//如果当前区间被覆盖,直接更新数据
{
tree[k].sum=1;
tree[k].ll=tree[k].rr=true;
}
else//如果没被覆盖,则用子节点更新
{
tree[k].ll=tree[k<<1].ll;
tree[k].rr=tree[k<<1|1].rr;
tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
if(tree[k<<1].rr&&tree[k<<1|1].ll)//如果左子树的右端点和右子树的左端点同时被覆盖,则区间段减一,有点区间合并的意思
tree[k].sum--;
}
}
void init(int k,int l,int r)
{
tree[k].ll=tree[k].rr=false;
tree[k].l=l;
tree[k].r=r;
tree[k].sum=tree[k].cover=0;
}
void build(int k,int l,int r)
{
init(k,l,r);
if(l==r)//叶子结点需要特别更新一下其子节点,因为下面update函数在叶子结点时可能直接调用pushup,若没有初始化则会因为之前的数据没有初始化而出错
{
init(k<<1,l,r);
init(k<<1|1,l,r);
return;
}
int mid=l+r>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void update(int k,int l,int r,int val)
{
if(l>tree[k].r||r<tree[k].l)
return;
if(tree[k].l>=l&&tree[k].r<=r)
{
tree[k].cover+=val;
pushup(k);
return;
}
int mid=tree[k].l+tree[k].r>>1;
update(k<<1,l,r,val);
update(k<<1|1,l,r,val);
pushup(k);//记得上传状态
}
vector<int>v;//离散化用
int get_id(int x)
{
return lower_bound(v.begin(),v.end(),x)-v.begin();
}
void dis_create()
{
v.push_back(-inf);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
}
struct Section
{
int l,r;
void input()//记得除了左右端点还需要加上一些空端点,不然不好判断区间段的连续性
{
scanf("%d%d",&l,&r);
v.push_back(2*l);
v.push_back(2*r);
v.push_back(2*l-1);
v.push_back(2*r+1);
v.push_back(2*l+1);
}
void dis_create()//更新离散化后的id
{
l=get_id(2*l);
r=get_id(2*r);
}
}a[N];
int main()
{
// freopen("input.txt","r",stdin);
int w;
cin>>w;
while(w--)
{
v.clear();
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
a[i].input();
dis_create();
for(int i=1;i<=n;i++)//将区间端点离散化
a[i].dis_create();
build(1,1,v.size());
for(int i=1;i<=n;i++)
update(1,a[i].l,a[i].r,1);
int ans=0;
for(int i=1;i<=n;i++)
{
update(1,a[i].l,a[i].r,-1);
ans=max(ans,tree[1].sum);
update(1,a[i].l,a[i].r,1);
}
printf("%d\n",ans);
}
return 0;
}