题意:给出一张俯视图。给出N个建筑物的左下标,长度,宽度,高度。现在求,从南面看,能看到那些建筑?
Sample Input
14 160 0 30 60 30 125 0 32 28 60 95 0 27 28 40 70 35 19 55 90 0 0 60 35 80 0 40 29 20 60 35 40 25 45 80 0 67 25 20 50 0 92 90 20 80 95 38 55 12 50 95 60 60 13 30 95 80 45 25 50 165 65 15 15 25 165 85 10 15 35 0
Sample Output
For map #1, the visible buildings are numbered as follows: 5 9 4 3 10 2 1 14 unique()函数是一个去重函数,STL中unique的函数 unique的功能是去除相邻的重复元素(只保留一个)还有一个容易忽视的特性是它并不真正把重复的元素删除。eg.int num[100]; unique(num,mun+n)返回的是num去重后的尾地址,之所以说比不真正把重复的元素删除,其实是,该函数把重复的元素一到后面去了,然后依然保存到了原数组中,然后返回去重后最后一个元素的地址,因为unique去除的是相邻的重复元素,所以一般用之前都会要排一下序。
离散化
基本思想就是在众多可能的情况中“只考虑我需要用的值”。
eg.1
给定平面上n个点的坐标,求能够覆盖所有这些点的最小矩形面积。这个问题难就难在,这个矩形可以倾斜放置(边不必平行于坐标轴)。
这里的倾斜放置很不好处理,因为我们不知道这个矩形最终会倾斜多少度。假设我们知道这个矩形的倾角是α,那么答案就很简单了:
矩形面积最小时四条边一定都挨着某个点。也就是说,四条边的斜率已经都知道了的话,只需要让这些边从外面不断逼近这个点集直到碰到了某个点。
你不必知道这个具体应该怎么实现,只需要理解这可以通过某种方法计算出来,毕竟我们的重点在下面的过程。
我们的算法很显然了:枚举矩形的倾角,对于每一个倾角,我们都能计算出最小的矩形面积,最后取一个最小值。
这个算法是否是正确的呢?我们不能说它是否正确,因为它根本不可能实现。矩形的倾角是一个实数,它有无数种可能,你永远不可能枚举每一种情况。我们说,矩形的倾角是一个“连续的”变量,它是我们无法枚举这个倾角的根本原因。我们需要一种方法,把这个“连续的”变量变成一个一个的值,变成一个“离散的”变量。这个过程也就是所谓的离散化。
我们可以证明,最小面积的矩形不但要求四条边上都有一个点,而且还要求至少一条边上有两个或两个以上的点。试想,如果每条边上都只有一个点,则我们总可以把这个矩形旋转一点使得这个矩形变“松”,从而有余地得到更小的矩形。于是我们发现,矩形的某条边的斜率必然与某两点的连线相同。如果我们计算出了所有过两点的直线的倾角,那么α的取值只有可能是这些倾角或它减去90度后的角(直线按“\”方向倾斜时)这么C(n,2)种。我们说,这个“倾角”已经被我们 “离散化”了。虽然这个算法仍然有优化的余地,但此时我们已经达到了本文开头所说的目的。
eg.2
大意是给定平面上的n个矩形(坐标为整数,矩形与矩形之间可能有重叠的部分),求其覆盖的总面积。平常的想法就是开一个与二维坐标规模相当的二维Boolean数组模拟矩形的“覆盖”(把矩形所在的位置填上True)。可惜这个想法在这里有些问题,因为这个题目中坐标范围相当大(坐标范围为-10^8到10^8之间的整数)。但我们发现,矩形的数量n<=100远远小于坐标范围。每个矩形会在横纵坐标上各“使用”两个值, 100个矩形的坐标也不过用了-10^8到10^8之间的200个值。也就是说,实际有用的值其实只有这么几个。这些值将作为新的坐标值重新划分整个平面,省去中间的若干坐标值没有影响。我们可以将坐标范围“离散化”到1到200之间的数,于是一个200*200的二维数组就足够了。实现方法正如本文开头所说的“排序后处理”。对横坐标(或纵坐标)进行一次排序并映射为1到2n的整数,同时记录新坐标的每两个相邻坐标之间在离散化前实际的距离是多少。这道题同样有优化的余地。
#include<iostream> #include<cstdio> #include<algorithm> 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<rhs.y); // true x未被rhs挡住(x在rhs.x的左边 或者 x与rhs.x相同,但x在rhs.x前面,即y<rhs.y) } }b[maxn]; int n; double x[maxn*2]; //用来存储各个建筑物的左右边界 bool cover(int i,double mx) //判断建筑物i在x=mx处是否可见 { return b[i].x<=mx&&b[i].x+b[i].w>=mx; //true时 b[i]在mx处可见 } bool visible(int i,double mx) { if(!cover(i,mx))return false; //b[i]在mx处不可见 for(int k=0;k<n;k++) if(b[k].y<b[i].y&&b[k].h>=b[i].h&&cover(k,mx))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; //建筑右边界 b[i].id=i+1; } sort(b,b+n); //按照结构体中重载的运算符<排序,x小的在前面,x相同y小的在前面 /*cout<<endl; for(int i=0;i<n;i++) cout<<b[i].id<<" "<<b[i].x<<" "<<b[i].y<<" "<<b[i].h<<" "<<b[i].d<<endl;*/ sort(x,x+n*2); int m=unique(x,x+n*2)-x; //x坐标排序后去重,得到m个坐标 //x的前m项为建筑群从左到右的左边界有边界 //for(int i=0;i<n*2;i++)cout<<x[i]<<endl; 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(b[i].x+b[i].w<x[j])break; //增添这句后,简化程序,当b[i]建筑的右边界都小于x[j]时在其中点处肯定不可见 if(visible(i,(x[j]+x[j+1])/2)){ //true b[i]在各个左右边界之间的中点处可见 vis=true; break; } } if(vis)printf(" %d",b[i].id); } printf("\n"); } //system("pause"); return 0; }