[POJ] 3277 .City Horizon(离散+线段树)

来自这两篇博客的总结

http://blog.csdn.net/SunnyYoona/article/details/43938355

http://m.blog.csdn.net/blog/mr_zys/10226425

题目大意

如图所示,在一条水平线上有N个建筑物,建筑物都是长方形的,且可以互相遮盖。给出每个建筑物的左右坐标值Ai,Bi以及每个建筑物的高Hi,需要计算出这些建筑物总共覆盖的面积。

题目数据范围: 
建筑物个数N:1 <= N <= 40000 
建筑物左右坐标值Ai, Bi:1 <= Ai,Bi <= 10^9 
建筑物的高度Hi:1 <= Hi <= 10^9

这里写图片描述

分析

由题意可以知道,这道题需要求的即是这些矩形的面积并。考虑到题目中一个特殊的条件,所有的矩形的一边在一条直线上,我们可以好好利用这个条件:由于所有的矩形在这条直线上的投影均与矩形的一个边长相等。所以,我们可以把矩形“压缩”成直线上的线段,且每条线段都有一个权值,这个权值就是矩形的高度Hi。那么,我们就可以利用线段树进行处理,计算面积并就相当于计算带权的线段并,即S = H * (B – A)。当某条线段被多次覆盖时(比如图中的线段A2B1),只取H值最大的进行计算。如图2.1中的矩形面积并为:S = H1*(B1 – A1) + H2 * (A3 – B2) + H3 * (B3 – A3) 基本思路清楚了,我们现在来考虑具体实现。

由于题目中矩形的左右坐标的范围非常大(1 <= Ai,Bi <= 10^9),如果建立大小为[1, 10^9)的线段树则会占用大量的空间。我们采用一种离散化的思想来处理这个问题,这种思路在线段树的题目中也是经常会用到的。考虑到一共只有N <= 40000个矩形,那么,这些矩形一共也只有2 * 40000 = 80000个左右坐标值。我们首先将这80000个坐标值按大小排序,对排序后的坐标依次赋予一个新坐标值k(1 <= k <= 80000),这样我们就把长度为[1, 10^9)的线段离散化成[1,80000)的线段了,而最后计算结果时,只需要按照新坐标值找回原始坐标值并代入计算即可。

 

举一个简单的例子,假设现在有三条线段[20,60),[10,50),[5,55)。我们将这三条线段的左右端点进行排序,其结果为5,10,20,50,55,60。我们将它们依次赋上新值1,2,3,4,5, 6。这样原始的三条线段被离散化为[3,6),[2,4),[1,5),我们就可以在[1,6)的空间内对其进行处理了。这就是离散化的威力。

 

回到原问题上来,当矩形所投影的线段被离散化以后,我们就可以建立线段树了。与之前讲过的初始化略有不同,现在每个线段树的节点不只是记录其所代表的线段是否被覆盖,而且要记录被覆盖的线段的权值。每次加入一个矩形就是在线段树上插入一条带权的线段,插入的实现过程与之前的也有不同。如果当前线段完全覆盖了节点所代表的线段,那么比较这两个线段的权值大小。如果节点所代表的线段的权值小或者在之前根本未被覆盖,则将其权值更新为当前线段的权值。

 1 #include <stdio.h>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 #define maxn 40400
 6 int max_h[maxn*10];
 7 int line[maxn<<2];
 8 struct buildings
 9 {
10     int a;
11     int b;
12     int h;
13 } B[maxn];
14 void build(int l,int r,int rt)
15 {
16     max_h[rt] = 0;
17     if(l + 1== r) return;
18     int mid = (l + r) >> 1;
19     build(l,mid,rt<<1);
20     build(mid,r,rt<<1|1);
21 }
22 void update(int l,int r,int rt,int L,int R,int h)
23 {
24     if(line[l] == L && line[r] == R) //若插入的线段完全覆盖当前节点所表示的线段
25     {
26         max_h[rt] = max(max_h[rt],h);
27         return ;
28     }
29     int m = (l + r) >> 1;
30     int mid = line[m];
31     if(R <= mid) update(l,m,rt<<1,L,R,h);// 当前节点的左子节点所代表的线段包含插入的线段
32     else if(L >= mid) update(m,r,rt<<1|1,L,R,h); // 当前节点的右子节点所代表的线段包含插入的线段
33     else  // 插入的线段跨越了当前节点所代表线段的中点
34     {
35         update(l,m,rt<<1,L,mid,h);
36         update(m,r,rt<<1|1,mid,R,h);
37     }
38 }
39 long long solve(int l,int r,int rt,int h)
40 {
41     //如果rt结点的值大,那么rt的子孙结点都是该值
42     if( h > max_h[rt]) max_h[rt] = h;
43     if(l + 1 == r) return (long long) (line[r] - line[l]) * max_h[rt];
44     int mid = (l + r) >> 1;
45     return solve(l,mid,rt<<1,max_h[rt]) + solve(mid,r,rt<<1|1,max_h[rt]);
46 }
47 int main()
48 {
49     int n,cnt;
50     scanf("%d",&n);
51     cnt = 0;
52     for(int i = 0; i < n; i++)
53     {
54         scanf("%d%d%d",&B[i].a,&B[i].b,&B[i].h);
55         line[++cnt] = B[i].a;
56         line[++cnt] = B[i].b;
57     }
58     sort(line+1,line+1+cnt);
59     cnt = unique(line+1,line+1+cnt) - line -1;//离散处理
60     build(1,cnt,1);
61     for(int i = 0; i < n; i++) update(1,cnt,1,B[i].a,B[i].b,B[i].h);
62     printf("%lld\n",solve(1,cnt,1,0));
63     return 0;
64 }

 

posted on 2015-04-16 17:49  tsw123  阅读(357)  评论(0编辑  收藏  举报

导航