线段树 大楼投影面积

线段树 大楼投影面积

 

问题:楼群的投影面积问题。有一群大楼,已知其位置、宽度和高度,设想楼群背后有一面垂直于地面的巨型屏幕,
水平方向的太阳光将楼群正投影到屏幕上,求投影总面积。
输入:	m(大楼数量,1<=m<=50 000),
	(ai,bi,hi)(大楼起始坐标、终止坐标和高度,in[0,108])。
输出:总投影面积。(提示:用离散化线段树。)
来源:俞经善等《基础训练题解》,哈工大出版社,2012,P257
样例输入:
5
1 5 10
2 6 30
3 7 20
4 6 15
5 10 5
样例输出:
165

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;


const int MAXN =5e5+6;
//输入的坐标和高度
struct line{
	int left,right;
	int height;
}lines[MAXN];
//线段树结构体
struct treeNode{
	int left,right;
	int height;
}trees[4*MAXN];
//离散化数组
int arr[4*MAXN];

bool cmp(const line &a,const line &b){
	return a.height<b.height;
}

int M;
//首先初始化 
void init(){
	
	memset(trees,0,sizeof(trees));
	memset(arr,0,sizeof(arr));
	memset(lines,0,sizeof(lines));

	scanf("%d",&M);
	for(int i=1;i<=M;i++){
		scanf("%d%d%d",&lines[i].left,&lines[i].right,&lines[i].height);
		arr[i*2-1] = lines[i].left;
		arr[i*2] = lines[i].right;
	}
    //这里排序的目的是为了后续插入面积的时候从小到大,这样小的面积就会被覆盖
    //保证了结果的正确性
	sort(lines+1,lines+M+1,cmp);
    //离散化,arr目前保存了所有的坐标点
	sort(arr+1,arr+2*M+1);
	
	int j=1;
    //取出重复的点,我们的目的只要保证每个坐标都有对应的数组映射即可
	for(int i=1;i<=2*M;i++){
		if(arr[j]!=arr[i])arr[++j] = arr[i];
	}
	arr[0] = j;
}

//创建  左闭右开 
//建树,这里就是把对应的左右点赋值即可
void create(int root,int left,int right){
	trees[root].left = arr[left];
	trees[root].right = arr[right];
//	trees[root].height = -1;
	if(right-left==1) return;
	
	int mid = (right+left)/2;
	create(root*2,left,mid);
	create(root*2+1,mid,right);
}
//更新
void update(int root,int left,int right,int height){
	
	//没有交集,直接跳过 
	if(left>=trees[root].right || right<= trees[root].left) return;
	//这个节点在更新范围之内,直接跟新 
	if(left<=trees[root].left && right>=trees[root].right ){
		trees[root].height = height;
		return;
	}
	
	//对应的,如果更新没有在根上全部更新,那么说明根将不能准确记录这个区间的
	//的高度了,所以要进行拆分,因为这里的部分更新了,所以计算的时候,递归计算到
	//他的子节点 
	if(trees[root].height>0)
		trees[root*2].height = trees[root*2+1].height = trees[root].height;
	//消除标记 
	trees[root].height = -1;
	
	update(root*2,left,right,height);
	update(root*2+1,left,right,height);
	
	//犯错的地方,对应的原来的增加区间是不变的,自己另外计算,搞混淆了 
//	int mid = (right+left)/2;
//	update(root*2,left,mid,height);
//	update(root*2+1,mid,right,height);
}

void insert(int ileft, int iright, int value, int p)
{
	if (ileft <= trees[p].left && trees[p].right <= iright) {
		trees[p].height = value;
		return;
	}
	if (trees[p].height > 0)
		trees[p*2].height = trees[p*2+1].height = trees[p].height;
	trees[p].height = -1;		// -1:清除标记
	if (iright > trees[p*2].right)
		insert(ileft, iright, value, p*2+1);	// 右半
	if (ileft < trees[p*2].right)
		insert(ileft, iright, value, p*2);	// 左半
	return;
}


//这种计算方式将导致错误,为什么呢,因为区间不同 
int quary(int root,int left,int right){
	if(left>=trees[root].right || right<= trees[root].left) return 0;
	if(left<=trees[root].left && right>=trees[root].right && trees[root].height>=0){
		printf("left%d right%d\n",trees[root].left,trees[root].right);
		return trees[root].height*(trees[root].right-trees[root].left);
	}
	int ans = 0;
	//查询区间不能变,又犯了同样的错误 
//	int mid= (left+right)/2;
//	ans+=quary(root*2,left,mid);
//	ans+=quary(root*2+1,mid,right);

	ans+=quary(root*2,left,right);
	ans+=quary(root*2+1,left,right);
		
	return ans;
}

int search(int p)
{
	if (trees[p].height >= 0){
		printf("left%d right%d\n",trees[p].left,trees[p].right);
		return (trees[p].height * (trees[p].right - trees[p].left));
	}
	// 计算面积
	// 此时treenode[p]. hi == -1
	return (search(p*2) + search(p*2+1));
}


int main(){
	init();
	create(1,1,arr[0]);
	for(int i=1;i<=M;i++){
		update(1,lines[i].left,lines[i].right,lines[i].height);
		//或 
//		insert(lines[i].left, lines[i].right, lines[i].height, 1);
	}
	
	printf("%d\n",arr[arr[0]]);
	
	int ans = quary(1,1,arr[arr[0]]);
	//或 
//	int ans = search(1);
	printf("%d\n",ans);
	return 0;
}

对于代码的总结:

最终的问题在于,区间要搞明白,查询区间和更新区间都是在这个递归过程中时不能变的,因为你已经在建树的时候建立好了每个节点的左右子树,离散化的时候要理清楚对应的左闭右开是不是有影响

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值