线段树 大楼投影面积
问题:楼群的投影面积问题。有一群大楼,已知其位置、宽度和高度,设想楼群背后有一面垂直于地面的巨型屏幕,
水平方向的太阳光将楼群正投影到屏幕上,求投影总面积。
输入: 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;
}
对于代码的总结:
最终的问题在于,区间要搞明白,查询区间和更新区间都是在这个递归过程中时不能变的,因为你已经在建树的时候建立好了每个节点的左右子树,离散化的时候要理清楚对应的左闭右开是不是有影响