CodeForces - 1285E Delete a Segmen(线段树+区间合并+离散化)

105 篇文章 4 订阅
14 篇文章 0 订阅

题目链接:点击查看

题目大意:给出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;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值