Mayor's posters OpenJ_Bailian - 2528+区间修改,区间查询——线段树

题目链接

***题目大意:(简化后)
·给你n个区间的信息,包括区间左端点和区间右端点,需要自己给区间赋值编号
·使用给定的n个区间的信息,更新维护的树的信息
·最后树的叶子节点上有几种信息。
题目数据:
 1 <= n <= 10000,n:表示给你几组区间的值;
 1 <= li <= ri <= 10000000,li表示区间的左端点,ri表示区间的右端点值;
限制: 
Time limit:1000 ms
Memory limit:65536 kB
题目分析:
区间最大的值是:1e7,虽然我们可以维护的一棵树的最大叶节点树木可以达到1e7,但是时间限制在1000m。不可能去维护和操作一棵树。
但是我们注意到题目中,n最大1e4。也就是最多有2e4个点,那么就可以离散化了,使得数据范围在2e4以内,维护一个记录编号的树就可以了。
*问题来了,怎么维护一棵以标号为信息的树呢?
***树是记录的标号,那么树的几点自然也是记录的树的节点了。
***当两个子节点相同时,父节点可以表示子节点的信息;
***当两个子节点不同时,父节点不能表示子节点的信息,可以用 0 来表示。
(不使用lazy标记)
*一个种重要的地方:
如何离散化所有的数据:(不适用去重函数,手动模拟,去重函数unique,虽然好使,但是对于某些变形题目可能不太友好)
//输入数据的代码,因为需要离散化所有输入的数据,所以用一个date结构体数组保存下来。
//date,有两个数据,其中一个id,记录数据;pos,记录输入的数据的位置,(第几个区间的数据
//pos==1,pos==3,pos==5.分别是第一个区间,第二个区间,第三个区间的左端点
//pos==2, pos==4,pos==6.,分别是第一个区间,第二个区间,第三个区间的又端点(有没有发现什么呢?)
for(i=1;i<=2*n;i++)
{
	scanf("%d",&date[i].id);
	date[i].pos=i;
}
//离散化的函数:
void simplify(void)
{
	sort(date+1,date+2*n+1,cmp);//对date数组里面的  2*n  个数排序(好几次写成了n个数)
	date[0].id=0;
	len=0;
	/*
	1 2
	4 5
	6 8
	自己模拟一边-输入数据-》排序-》按下面代码离散化处理,自然理解这个函数的意思。
	*/
	for(int i=1;i<=2*n;i++)
	{
		if(date[i].id - date[i-1].id >=1)
		{
			++len;
		}
		
		if(date[i].pos&1)
		{
			int k=(date[i].pos+2)/2;
			c[k].L=len;
			c[k].id=k;
		}
		else
		{
			c[(date[i].pos)/2].R=len;
		}
	}
	return ;
}

《下面代码中的一些重要步骤有注释》

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int num=20010;
struct node//记录离散后区间的端点,编号
{
	int L,R;
	int id;
}c[num];
struct point //维护一棵以标号为叶节点的树
{
	int L,R,id;
}a[num*4];
struct base//用于离散处理数据
{
	int id;
	int pos;
}date[num*2];
bool book[num*2];//用来查询阶段的标记
int n,len;
bool cmp(base s1,base s2)//对date结构体按照id由大到小排序
{
	return s1.id < s2.id;
}
void simplify(void)//上面有解释
{
	sort(date+1,date+2*n+1,cmp);
	date[0].id=0;
	len=0;
	for(int i=1;i<=2*n;i++)
	{
		if(date[i].id - date[i-1].id >=1)
		{
			++len;
		}
		
		if(date[i].pos&1)
		{
			int k=(date[i].pos+2)/2;
			c[k].L=len;
			c[k].id=k;
		}
		else
		{
			c[(date[i].pos)/2].R=len;
		}
	}
	return ;
}
void build(int k,int L,int R)//建树
{
	a[k].L=L;a[k].R=R;a[k].id=0;//所有节点的标号都初始化为0
	if(L==R) return ;//到叶节点之后返回
	int mid=(L+R)>>1;
	build(k<<1,L,mid);//k<<1==k*2,
	build(k<<1|1,mid+1,R);//k<<1|1==k*2+1
	//这里不需要update函数,进行更新
	return ;
}
//维护树的关键:
//如果一个节点的两个子节点的信息相同,那么就可以用父节点的信息表示子节点的信息,此时更新父节点的信息;
//如果两个子节点的信息不同,那么父节点不可以表示子节点的信息,标记为 0 .
void update(int k)
{
	if(a[k<<1].id==a[k<<1|1].id) a[k].id=a[k<<1].id;
	else a[k].id=0;
	return ;
}
//由大区间的信息更新小区间的信息
void pushdown(int k)
{
	a[k<<1].id=a[k<<1|1].id=a[k].id;
	return ;
}
void change(int k,int L,int R,int x)
{
	if(a[k].L >= L && a[k].R <=R)
	{
		a[k].id=x;
		return ;
	}
	if(a[k].id) pushdown(k);
	int mid=(a[k].L + a[k].R)>>1;
	if(L<=mid) change(k<<1,L,R,x);
	if(R>mid)  change(k<<1|1,L,R,x);
	
	update(k);//更新父节点
	return ;
}
void search(int k,int L,int R)
{
	if(a[k].id&&a[k].L >= L && a[k].R<=R)//a[i].id不为0,表示下面的小区间的值可以用大区间进行表示
	{
		book[a[k].id]=1;
		return ;
	}
	if(a[k].id) pushdown(k);
	
	int mid=(a[k].L + a[k].R)>>1;
	if(L<=mid) search(k<<1,L,R);
	if(R>mid)  search(k<<1|1,L,R);
	
	update(k);
	return ;
}
int main()
{
	int t,s;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		int i,j;
		for(i=1;i<=2*n;i++)
		{
			scanf("%d",&date[i].id);
			date[i].pos=i;
		}
		simplify();//离散
		build(1,1,len);//建树
		for(i=1;i<=n;i++)
		{
		  //注:c结构体数组的值在离散化的时候更新的
			change(1,c[i].L,c[i].R,c[i].id);//按照输入的顺序,修改区间
		}
		for(int i=1;i<=len;i++) book[i]=0;//标记找到了什么编号
		search(1,1,len);
		long long ans=0;
		for(int i=1;i<=len;i++) if(book[i]==1) ans++;
		printf("%lld\n",ans);
	} 
	return 0;
}
有什么疑问可以加QQ好友(3237676809)(请备注csdn+你的名字),或者在下方评论。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值