5.4 例题 5-12 城市正视图

5.4 例题5-12城市正视图

题目链接:https://vjudge.net/problem/UVA-221


    有n个建筑物。左侧是俯视图(左上角是建筑物编号,右下角是高度),右侧是从南向北看的正视图。

    输入每个建筑物左下角坐标(即x,y坐标的最小值),宽度(即x方向的长度),深度(即y方向的长度)和高度(以上数据均为实数),输出正视图中能看到的所有建筑物,按照左下角x坐标从小到大进行排序。左下角坐标相同时,按照y坐标从小到大排序。输入保证不同的x坐标不会很接近(即任意两个x坐标要么完全相同,要么差别足够大,不会引起精度问题)。


把所有x坐标排序去重,则任意两个相邻x坐标形成的区间具有相同属性,一个区间要么完全可见,要么完全不可见。这样,只需在这个区间里任选一个点(例如中点),就能判断出一个建筑物是否在整个区间内可见。如何判断一个建筑物是否在某个x坐标处可见呢?首先,建筑物的坐标中必须包含这个x坐标,其次,建筑物南边不能有另外一个建筑物也包含这个坐标,并且不比它矮。


先讲讲题目思路:

我们来个简单的模型(假设下面四个楼房一样高):


现在我们来想想,如果我们从前向后看,能看到几个楼房?(楼房都一样高)

显然是3号、2号和4号。


那么怎么让计算机解决这个问题呢?

我们判断建筑物是否可见,我们是不是可以枚举从 3号楼最左端到4号楼的最右端 所有的x坐标,看看该建筑物是否在x轴的某个范围内可见,这个在我们人脑中想一下就可以得出答案,但是计算机可不能这样处理问题,因为从 3号楼最右端到4号楼的最左端 的x坐标有无数个,这样一来我们只能将问题离散化 ,怎么离散化呢?看下图:


我们将 3号楼最左端到4号楼的最右端的区间给分割成6个小区间,这样一来我们就会发现每个区间只有两种状态:1. 存在楼房,2. 不存在楼房。


那么我们怎么知道这个区间内是否存在楼房呢?我们可以在区间中任意取一点,然后判断这点是否属于某栋楼房的范围,这个点我们就可以取中点。


现在我们最后的问题是怎么判断某栋楼是否在某个区间中可见,必须满足下面两个条件:

1.建筑物必须包含这个区间(前面讲到,只须判断区间中点是否在建筑物范围内即可)

2.建筑物的南面不能有和其处于同一区间并且比其高的建筑物(这个我们只须遍历所有楼房即可)


好了,再来看看代码怎么实现:


#include

#include

#include

using namespace std;


const int maxn = 100 + 5;

struct Building{

    int id;

    double x, y, w, d, h;

    bool operator < (const Building& rhs) const{         //重载运算符<

         return x < rhs.x || (x == rhs.x && y

    }

}b[maxn];


int n;

double x[maxn* 2];             //用来存储各个建筑物的左右坐标


bool cover(int i, double mx)                  //判断建筑物i在该区间内(以该区间中点mx做判断)

{

    return b[i].x <= mx && b[i].x + b[i].w >= mx;        //true时  b[i]的x坐标有包含该区间,所以在该区间内

}


bool visible(int i, double mx)

{

    if (!cover(i, mx)) return false;           //判断b[i]是否在该区间内

    for (int k = 0; k < n;k++)

         if (b[k].y < b[i].y &&b[k].h >= b[i].h &&cover(k, mx))            //判断b[i]在该区间是否会被前面的建筑挡到

    return false;                            //b[i]被b[k]挡住

    return true;                                   //没被挡到,b[i]在mx处可见

}


int main()

{

    int kase = 0;

    while (scanf("%d", &n) == 1&& n) {                      //n个建筑

         for (int i = 0; i < n;i++) {

             scanf("%lf%lf%lf%lf%lf",&b[i].x, &b[i].y, &b[i].w, &b[i].d, &b[i].h);

             x[i* 2] = b[i].x;                   

             x[i* 2 + 1] = b[i].x + b[i].w;        // 在存入数据的同时将其首末位置都存入x数组中

             b[i].id= i + 1;

         }

         sort(b,b + n);               //按照结构体中重载的运算符<排序,x小的在前面,x相同y小的在前面

         cout<

        // for(inti=0;i

        // cout<

         sort(x,x + n * 2);

         int m = unique(x, x + n* 2) - x;               //x坐标排序后去重,得到m个坐标


       //  for(int i=0;i

       // cout<

         if (kase++)printf("\n");

         printf("For map #%d,the visible buildings are numbered as

follows:\n%d", kase, b[0].id); //左下角的建筑肯定可以看到

         for (int i = 1; i < n;i++) {

             bool vis = false;

             for (int j = 0; j < m -1; j++) {


                  if(visible(i, (x[j] +x[j + 1]) / 2)) {      

                      vis=true;

                      break;

                  }    //如果true  b[i]在各个区间之间的中点处可见

             }

             if (vis) printf(" %d", b[i].id);

         }

         printf("\n");

    }

    return0;

}

代码思路:

    1、定义结构体,并重载运算符<,用于排序。然后声明一个结构体数组b,在声明一个数组x,用于存储区间

    2、输入并存储数据(b记录建筑的每个数据,x记录每个建筑的两个x坐标点)

    3、将两个数组排序,然后将x数组去重,得到离散化后的区间,并求出区间数量m

    4、输出题目要求格式,将第一个b[0]输出(排序后b[0]的x,y都最小,必能看见)

    5、依次对每一个建筑,遍历每一个区间,判断看见的条件是否成立:

                    5.1  是否在该区间内 (是就进行5.2,否则条件不成立,break)

                    5.2  在该区间内,是否为第一个建筑 (是就说明可看见条件成立,break,否则进行5.3)

                    5.3  在该区间内,不是第一个建筑,再遍历该区间排在其前面的建筑,是否都比它们高    

                        (是就可以看见,条件成立,否则break)

    6、以上判断,条件成立的(看bool值),即可输出该建筑编号

    7、等待下一次输入,或者结束

(想做流程图的,发现很耗时间,就直接文字了)


题目知识点:

    1、 从结果看题:一开始看到题,会想不到这么多数据怎么用,但看到结果,是一种平视图,就应该想到这里可以减少一种数据,建筑的深度,因为它不影响判断。再看图,影响结果的有建筑的编号,长度,高度,还有潜在的位置(y轴),找到大概的判断路径(x->y->h)。

    2、数据离散化:当数据只与它们之间的相对大小有关,而与具体是多少无关时,可以进行离散化。使用STL算法离散化,思路是:先排序,再删除重复元素,最后就是索引元素离散化后对应的值。在这道题上就是要将不同建筑的x坐标转化为区间,用独立的区间来判断里面是否可以看见建筑,有几座。同时可以看到,建筑的深度这个数据是没作用的。


 代码知识点:

    1、结构体重载运算符再用sort:sort函数默认下排序结构体,会以第一个变量,做升序。这里代码重载运算符<,达到的效果是:两个建筑先比较x坐标,如果相同,再比较y坐标,保证最后b[0]存储的是最左下的建筑,并在后面的区间遍历比较有作用。这个点扩展我对sort的理解,原来以为只是一个排序功能的函数,通过重载运算符,可以进行更高级的排序,相信肯定有不少其他函数也有这种功能。

    2、unique函数:unique的作用是去掉容器中相邻元素的重复元素,该函数不能改变包含元素范围的对象的属性(即,它不能改变数组或容器的大小)通过用下一个不重复的元素替换重复元素来完成删除,而返回值是去重之后的 尾地址!

举个例子:

int num[10]={1,1,2,2,2,3,4,5,5,5};

int ans=unique(num,num+10)-num;

这时,返回的ans是5,而num中前5项就是1,2,3,4,5,一般使用前需要对容器进行排序,这样才能实现对整个数组去重。

有一点:

for (int i = 0; i < 10; i++)

cout << num[i];

输出的是:1 2 3 4 5 3 4 5 5 5

说明该函数将重复的数值删除了(通过用下一个不重复的元素替换重复元素来完成删除)达到不重复的全部元素后,后面元素就不理了

但在网上查询的过程中,有很多文章说的是,unique去重的过程是将重复的元素移到容器的后面去,实际上这种说法并不正确,应该是把不重复的元素移到前面来。

你可能感兴趣的:(5.4 例题 5-12 城市正视图)