0)
思路很好理解,但是写的过程中很容易写错!
①:sort排序时,其他点相对最左下角的点,先按极角由小到大排序,极角相同,按距离由近到远排序。(会留下最后进栈的,也就是留下距离远的,是对的)
②:Graham()里的while条件语句中,points[i]是和已经进栈的sstack[top]、sstack[top-1]比较,而不是points[top]、points[top-1]!
③:Graham()里的while条件语句while ((1<top) && (mulity(points[i], sstack[top], sstack[top - 1])>0)); ,之前在后面多加一个 ; 而出现各种奇怪答案 ,不要小看这一个分号,编译器不报错,而且会先认为是一个执行语句并执行,while则不判断就认为是TRUE,执行一遍while里的内容,然后再正常判断while里的条件语句是真是假!!
关于细节:
①:如果在Graham()里的while条件语句中出现三点(points[i]、sstack[top]、sstack[top-1])一线的情况时,若写mulity()>0,则top++,points[i]入栈,即三个点都入栈;如果写while()中写mulity()>=0,则三点一线时,top位置的点出栈,即第二个点出栈,最终只保留该直线第一个点和最后一个点。这两种情况最后得出的凸包的周长是相同的,但是凸包上的点是不同的,后者有舍弃,前者没有,所以注意这里可以出变型的题。
②:在sort的cmp自定义排序函数中,如果极角相同,应该距离近的在前面是有必要的。考虑当向量方向相同,距离短的在前(假设是B点),长的(假设是C点)在后,这样在Graham里的while循环判断中,如果恰好出现这样的极端情况:最左下角的点(假设是A)位于栈里的top-1的位置,B点位于top,C点位于i,则top--,将点A出栈,将点B进栈,然后计算距离时,这一段是计算点A到点C,正解。如果是距离远的在前,那么应当是点B位于i,点C位于top,那么while判断里因为极角相同则top--,点C出栈,点B进栈,计算这一段距离时,则是A到B,错的。围成的凸包也不对。
③:关于叉乘、判断方向以及如何进出“栈”,等等,都在代码中有详细注释,就不再一一赘述。
1)
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
const int maxn = 110;
struct Point{
int x;
int y;
}points[maxn], sstack[maxn];
int top;
int n;
int mulity(Point p1, Point p2, Point p0){//注意,这里直接返回计算结果就行,不要再判断什么情况下返回1什么情况下返回0,因为这是重用度比较高的函数,是位于“底层”的,直接返回结果到上层中判断远比自己加一个判断要好得多!
return (p1.x - p0.x)*(p2.y - p0.y) - (p2.x - p0.x)*(p1.y - p0.y);//向量p0p1 到 向量p0p2,叉乘大于0,意味着,右手定则右手四指指向p0p1的方向握向p0p2的方向时,大拇指朝外
}
double dis(Point a, Point b){
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
bool cmp(Point p1, Point p2){//p0p1 到 p0p2
//向量方向相同,距离短的在前(假设是B点),长的(假设是C点)在后,这样在Graham里的while循环判断中,如果恰好出现这样的极端情况:最左下角的点(假设是A)位于栈里的top-1的位置,B点位于top,C点位于i,则top--,将点A出栈,将点B进栈,然后计算距离时,这一段是计算点A到点C,正解。如果是距离远的在前,那么应当是点B位于i,点C位于top,那么while判断里因为极角相同则top--,点C出栈,点B进栈,计算这一段距离时,则是A到B,错的。围成的凸包也不对。
int k = mulity(p1, p2, points[0]);
if (k>0){
return 1;
}
else if (k == 0 && dis(p1, points[0]) <= dis(p2, points[0])){
return 1;
}
return 0;
}
void Graham(){
top = -1;
for (int i = 0; i<n; i++){
//遇到i,比较i和当前栈顶元素top,谁在外围谁留下,即mulity(p1,p2,p0),右手由p0p1 握向 p0p2,拇指朝里,则叉乘<=0,所以p2靠外所以压栈,而p1在内所以出栈。
while ((1<top) && (mulity(points[i], sstack[top], sstack[top - 1])>0)){//mulity()>=0也是可以的
//while的判断语句,调试了一上午!一个是因为sstack之前用points代替,不和栈里的数做比较明显是错的,但这个地方确实很容易写错,高危!!!;另一个是因为多打了一个分号,则按while判断句按陈述语句先执行,然后因为检测到while里的判断句被执行了,所以while()内的语句一定先执行一次然后才进入下一次while里的条件语句进行判断要不要再进while循环!!
top--;//若将i与top-1两点相连,则当前top点在内侧。所以当前top点出栈。退回到上一个top点与top-1,继续比较,直到当i入栈时,已入栈的点不会被包裹在内侧。
}
//i点可入栈,并且不会破坏当前栈中所有点可构成凸包(即不会出现,连接栈中任意两点使得栈中任意一点会在连线内侧的情况)
sstack[++top] = points[i];
}
}
int main()
{
while (~scanf("%d", &n) && n != 0){
//输入数据,并且找到左下角的点
scanf("%d%d", &points[0].x, &points[0].y);
int ld_id = 0;//left down id
for (int i = 1; i<n; i++){
scanf("%d%d", &points[i].x, &points[i].y);
if (points[ld_id].y>points[i].y){
ld_id = i;
}
else if (points[ld_id].y == points[i].y&&points[ld_id].x>points[i].x){
ld_id = i;
}
}
if (n == 1){
printf("%.2f\n", 0.00);
continue;
}
else if (n == 2){
printf("%.2f\n", dis(points[0], points[1]));
continue;
}
//把左下角的点放到数组第一个
Point temp = points[0];
points[0] = points[ld_id];
points[ld_id] = temp;
//其他点按照与p0的极角由小到大排序,极角相同按距离由近到远排序,这样在Graham里的循环判断找凸包时,会将先进栈的最近的点踢出去,将距离最远的点留下,这样做所求的这一段距离才是对的,如果最后留下最近的点则形成的凸包漏掉了这个最远的点,所求距离自然也是过短的
sort(points + 1, points + (n), cmp);
//for(int i=0;i<n;i++)
//cout<<points[i].x<<" "<<points[i].y<<endl;
Graham();
double res = 0;//凸包周长
//机智的0到top的相加..
for (int i = 0; i <= top; i++){
res += dis(sstack[i], sstack[(i + 1) % (top + 1)]);
}
printf("%.2f\n", res);
}
}
2)
Description
The diameter and length of the trees are omitted, which means a tree can be seen as a point. The thickness of the rope is also omitted which means a rope can be seen as a line.
There are no more than 100 trees.
Input
Zero at line for number of trees terminates the input for your program.
Output
Sample Input
9 12 7 24 9 30 5 41 9 80 7 50 87 22 9 45 1 50 7 0
Sample Output
243.06