ZOJ 1010. Area 解题报告

    ZOJ 1010. Area.

    题目地址:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1010

    基本题意:此题目给出 n 个点的坐标 (xi,yi ) ,要求求出这些点按顺序围成的多边形的面积。同时要求检测出不合法的多边形(例如非相邻边彼此相交的情况)。

 

    

 

    此题求面积不难,样例很容易就过了,但是提交 10+ 次基本都是 WA ,几乎失去信心。做题最郁闷的就是你不知道问题到底出在什么地方,可能改了很多次都没有试对地方。后来跑到 88 的算法版搜了下,看到别人的代码,然后事后总结实际还是卡在了什么样的多边形是 Impossible 的,这个检测代码没有写好,耐心不够导致。

 

    (1)求面积很简单,类似求某个曲线的区间积分

 

    用所有多边形的边和X轴围成的梯形的面积累加起来就可以了。例如某条边 ( P1 - P2 ) 和在它在 x 轴的投影 ( p1' - P2' ) 所围成的梯形面积是:

 

    [ p1->p2 ] = 1/2 * ( y1 + y2 ) * ( x2 - x1 ) ;  ----(I)

 

    这个式子拆开后就是:

 

    S [ p1->p2 ] = 1/2 * ( -x1 * y1 + x2 * y2 - x1 * y2 + x2 * y1 ) ;  ---- ( 注:蓝色部分的 xi * yi 累加后被消掉 )

 

    由于每个边都被累加一遍,每个点参与了组成边两次(前一条边的终点和后一条边的起始点),所以每个顶点自身坐标的乘积 ( xi * yi ) 累加的时候都被消掉了!最后就是累加所有的式(I):

 

    Area = 1/2 * ( -x( i ) * y( i + 1 )  + x( i + 1 ) * y( i ) ) ;

        ----( i = 1,2,...,n,且P(n+1) = P(0) );     ----(II)

 

    上面的式子,括号内是两个矢量的叉积 P[ i + 1 ] × P[ i ]。因此式II实际上是依次累加相邻点的叉积,最后在除以2。考虑叉积的含义,是以两个矢量为相邻边的平行四边形(由 O(0,0), P1, P2, (P1 + P2) 四个点组成)的面积(在平面坐标中用三角形面积相减可以很容易推导出该含义),因此这个方法的物理意义就非常明显了,我们可以直观的假定原点位于多边形内部,显然依次把相邻点和原点围成的三角形的面积累加起来就是这些点组成的多边形的面积。因此 1/2 ( P[ i + 1 ] X P[ i ] ) 就是三角形 O - P[ i + 1 ] - P[ i ] 的面积(平行四边形面积的一半,O 表示原点)。当然,由于叉积有符号,所以原点位于多边形外部也成立,只是在内部更直观,更容易理解。

 

    备注:我们可以把存储顶点的数组扩充一个元素,然后把 P( n+1 ) 赋值为 P( 0 ),这样我们对上式的循环中就不必对顶点索引使用取模运算(MOD)。我自己写的代码采用的(I)这种计算方法,我参考的代码采用(II)中的计算方法,两者计算量相差不大,但后者不太直观,所以我就稍微解释了下两者本质是相同的。

    注意式(I)中的梯形面积是有符号的,和 P1,P2 的点顺序相关( S[ p1->p2 ] = -S[ p2->p1 ] ) ,因此 Area 的符号和顶点顺序有关,所以最后别忘了对计算结果求绝对值。

 

    (2)麻烦在于,什么样的多边形是 Impossible 的

 

    做题的时候肯定我们就会着重考虑这一点。因为不管任何多边形,按照(1)都能算出个面积,但是多边形是否是有效的需要单独判断。设多边形有 n 个顶点和边,则:

    2.1) n < 3 时,显然是 Impossible,因为一个点或一个线段不能组成多边形。

    2.2) n > 3 时,依次判断每条边 和 它的所有非相邻边 不能相交(包含交点位于某一条线段上的情况),保证这一点即可。

    2.3) n = 3 时,是三角形,无须做 2.2 中的检测。如果三角形的三个点共线的话,是不是应该输出 Impossible,我们没有考虑也 AC 了 (即我们会对这种情况认为是有效的而输出 0.00),估计 OL平台的测试输入也没有考虑这种情况。

    

    下面我们考虑2.2中的检测:

    总的边数为 n,则每条边的非相邻边为(n-3)条,所以检测完所有边的情况类似冒泡排序,以及绘制多边形所有顶点的连接线的金刚石图形,需要耗费O(n^2)的时间复杂度。

    相邻边必然是相交于顶点的所以不用检测,即使相邻边向内重叠了,比如连续的三个顶点:(0,0),(0,5),(0,3)这种情况也不必担心,因为在下一条边检测时也会检测出相交情况的。

    判断两个线段是否相交属于算法中的计算几何部分,主要原理是 如果两个线段相交,则从任何一个线段角度看,另一个线段的两个端点都位于它自己的两侧(该线段以任一端点为原点旋转到经过另一个线段的两个端点的时针方向相反)。 两个矢量的相对旋转方向利用矢量叉积求出。

    我原来还考虑了浮点数和 0 的比较判断,实际上最后从提交结果发现无需考虑这个问题,直接和 0 比就是了。

 

    (3)对顶点坐标的存储

 

    为了检测多边形是不是Impossible的,就必须要存储所有顶点的坐标。如果不需要判断这个,就不需要任何额外的存储空间。那样就简洁多了。当然那样的话这道题也就毫无任何难度,也失去了对计算几何的考察点。

    我看了我参考的解法,我居然没有想到像他开一个维度为1000的数组,ft!我是用了一个链表来存储所有的边。。。。汗。。。(当然应该用STL就可以,我习惯了自己写链表的代码),所以我的代码比我参考的那个人的多好多。。。(他的代码好简短!)

    另外,由于我用链表存储的是线段,相当于每个顶点被我存储了两次,所以实际上还是浪费了一倍的堆上内存。另外还要加上链表使用的指针占用的空间。当然优点是动态内存管理相比静态数组在内存的使用上要灵活一些,这就无须多做解释了。

    检测边是否相交实际上和那个人的代码的原理基本一致,这部分代码我基本是照着《算法导论》上的伪码直译过来的。

 

    我提交的代码如下:

 

CODE_ZOJ1010

#include <stdio.h>#include <stdlib.h>

//#define ZEROVALUE 0.00000000000001

#ifndef __max#define __max(a,b)  (((a) > (b)) ? (a) : (b))#endif

#ifndef __min#define __min(a,b)  (((a) < (b)) ? (a) : (b))#endif

typedef struct _LINE{double x1;double y1;double x2;double y2;    _LINE* pNext;} LINE;

//以Pi为原点,从 PiPj 到 PiPk 的方位(返回值的符号表示顺时针 or 逆时针)double Direction(double xi, double yi, //原点                 double xj, double yj, //线段1结束点                 double xk, double yk) //线段2结束点{return (xk - xi) * (yj - yi) - (xj - xi) * (yk - yi);}

//如果点 Pk 位于线段 PiPj 所在直线上,则它是否位于该线段内int OnSegment(double xi, double yi,                 double xj, double yj, //线段1结束点               double xk, double yk) //线段2结束点{if(xk >= __min(xi, xj) && xk <= __max(xi, xj)        && yk >= __min(yi, yj) && yk <= __max(yi, yj))return 1;elsereturn 0;}

//判断两个线段P1P2,P3P4是否相交int intersect(double x1, double y1,double x2, double y2,double x3, double y3,double x4, double y4){double d1 = Direction(x3, y3, x4, y4, x1, y1);double d2 = Direction(x3, y3, x4, y4, x2, y2);double d3 = Direction(x1, y1, x2, y2, x3, y3);double d4 = Direction(x1, y1, x2, y2, x4, y4);

if(    ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0))        && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)) )return 1; //相交

else if(d1 == 0 && OnSegment(x3, y3, x4, y4, x1, y1))return 1;else if(d2 == 0 && OnSegment(x3, y3, x4, y4, x2, y2))return 1;else if(d3 == 0 && OnSegment(x1, y1, x2, y2, x3, y3))return 1;else if(d4 == 0 && OnSegment(x1, y1, x2, y2, x4, y4))return 1;elsereturn 0; //绝对不相交}

/* 增加线段到链表尾部 */

void AddLine(LINE** ppHead, LINE** ppTail,double x1, double y1,double x2, double y2){    LINE *pNode = (LINE*)malloc(sizeof(LINE));    pNode->pNext = NULL;    pNode->x1 = x1;    pNode->y1 = y1;    pNode->x2 = x2;    pNode->y2 = y2;

if(*ppTail != NULL)    {        (*ppTail)->pNext = pNode;        }else    {        *ppHead = pNode;    }    *ppTail = pNode;}

void FreeList(LINE** ppHead, LINE** ppTail){    LINE* pNode = (*ppHead);while(pNode != NULL)    {        LINE* tmp = pNode->pNext;        free(pNode);        pNode = tmp;    }    *ppHead = NULL;    *ppTail = NULL;}

/* 检查是否这一系列点围成的多边形是否合法 */

int CheckValid(LINE* pHead){int index = 0; /* 当前正循环到的边的索引 */    LINE* pCur = pHead;    LINE* pTest;

while(pCur != NULL)    {        pTest = NULL;if(pCur->pNext != NULL)            pTest = pCur->pNext->pNext;

while(pTest != NULL)        {//最后一条边(n-1) -- (0),不与第一条边 (0) -- (1) 检测            if(index == 0 && pTest->pNext == NULL)break;

/* 非相邻边相交? */if(intersect(pCur->x1, pCur->y1, pCur->x2, pCur->y2,                pTest->x1, pTest->y1, pTest->x2, pTest->y2))            {return 0;            }            pTest = pTest->pNext;        }        pCur = pCur->pNext;        ++index;    }    return 1;}

int main(int argc, char* argv[]){    LINE *pHead = NULL, *pTail = NULL;int i, n = 0, figure = 1;double x0, y0, x1, y1, x2, y2;double area = 0;

//int test = intersect(0, 1, 1, 0, 1, -1, 0, 0.5);

while(1)    {        scanf("%d", &n);if(n <= 0)break;

//打印一个空行        if(figure > 1)            printf("\n");

area = 0;        scanf("%lf %lf", &x0, &y0);        x1 = x0;        y1 = y0;for(i = 1; i <= n; i++)        {if(i < n)                scanf("%lf %lf", &x2, &y2);else            {                x2 = x0;                y2 = y0;            }            area += (y1 + y2) * (x2 - x1) * 0.5;

AddLine(&pHead, &pTail, x1, y1, x2, y2);

x1 = x2;            y1 = y2;        }

if(area < 0)            area *= -1;

if(n < 3)            printf("Figure %d: Impossible\n", figure);else if(!CheckValid(pHead))            printf("Figure %d: Impossible\n", figure);else            printf("Figure %d: %.02lf\n", figure, area);

FreeList(&pHead, &pTail);

figure++;    }return 0;}

    

    另附上我参考的代码:

 

code_by_dynamic

发信人: dynamic (programming@math), 信区: ACMICPC标  题: Re: 1010里的错误多边形有多少可能性?发信站: 逸仙时空 Yat-sen Channel (Wed Apr  9 20:55:07 2003), 站内信件

我的程序

//zju 1010//by dynamic//on sunday july 28th

#include <stdio.h>

double det(double x1,double y1,double x2,double y2,double x3,double y3){return x1*y2+x2*y3+x3*y1-x2*y1-x3*y2-x1*y3;}

int main(){int n,i,j,count=0,temp;double x[1000],y[1000],area;while(1)        {                scanf("%d",&n);if (n==0) return 0;                count++;if (count>1) printf("\n");                printf("Figure %d: ",count);for(i=0;i<n;i++) scanf("%lf%lf",&x[i],&y[i]);if (n<3){                        printf("Impossible\n");continue;                }for(i=0;i<n;i++)for(j=(i+1)/n;j<i-1;j++){                                temp=(i+1)%n;if (det(x[j],y[j],x[i],y[i],x[j+1],y[j+1])*det(x[j],y[j],x[temp],y[temp],x[j+1],y[j+1])<=0&&                                        det(x[i],y[i],x[j],y[j],x[temp],y[temp])*det(x[i],y[i],x[j+1],y[j+1],x[temp],y[temp])<=0) {                                        printf("Impossible\n");goto quit;                                }                         }                area=0;for(i=0;i<n;i++){                        area+=x[i]*y[(i+1)%n];                        area-=y[i]*x[(i+1)%n];                }if (area<0) area*=-1;                printf("%.2lf\n",area/2);quit:;        }}

 

    参考资料:

    1. 《算法导论》,第33章(计算几何学)33.1 节(线段的性质)。

时间: 2024-10-23 16:32:00

ZOJ 1010. Area 解题报告的相关文章

杭电ACM 2000-&amp;gt;2099 100道题 详细解题报告出炉

我去年暑假花了5天,把杭电ACM网站上2000到2099这100道题全AC了,又花了10来天精心写解题报告.里面包括题目.解题思路.编程技巧以及参考源码.所有代码都是使用C/C++写的. 最近整理资料时无意间发现,打包成chm文件和大家分享.我已经上传到CSDN上了.下载地址:http://download.csdn.net/source/492194 也可到我的Google Sites上下载. 题号 题名 题号 题名 2000 ASCII码排序 2001 计算两点间的距离 2002 计算球体积

ZOJ 1111 - Poker Hands 解题报告

          Poker Hands (比较两手牌的大小)           http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=111           题目描述:这道题要求比较两手牌的大小.每手牌都有5张牌组成,牌的大小的定义如下,首先确定这组牌的所属种类是下面哪一种(若同属同一种类,则根据该分类后面的比较规则继续比较,所属种类越靠后牌越大).           ● High Card:杂牌(不属于下面任何一种).

ZOJ - 1098 Simple Computer 解题报告

Simple Computers Time Limit: 1 Second      Memory Limit: 32768 KB You are to write an interpreter for a simple computer. This computer uses a processor with a small number of machine instructions. Furthermore, it is equipped with 32 byte of memory, o

ZOJ 3505. Yet Another Set of Numbers 解题报告

ZOJ 3505:Yet Another Set of Numbers 地址:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3505   题意:有一个数字集合,集合中的数遵循以下规则:   ( 1 ). 每个数字的最高位不是 0 ; ( 2 ). 每个数字包含最多 N 位,且只有 0,1,2,3 这四个数字可能出现(0 < N < 20); ( 3 ). 每个数字的相邻位不同(例如:301是有效的,300不是); (

ZOJ 2529 - A+B in Hogwarts 解题报告

          题目2529:A+B in Hogwarts           链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1535           题目描述:哈利波特去的魔法学院使用一种特殊进制法表示数字:第i位用第i个素数为进制(radix),例如"个位"的进制为第一个素数2,"十位"的进制为第二个素数3,"百位"的进制为第三个素数5,...依此类推.例

ZOJ 1205 - Martian Addition 解题报告

          http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1205           题目说明:(把题目从GOOGLE翻译的结果修改而来)           在22世纪,科学家们发现智能居民生活在火星.火星人非常喜欢数学.每一年,他们将举行一次火星算术大赛(计算机) ,竞赛内容是计算两个100位数的和,使用时间最少的人获得冠军.今年,他们还邀请地球上的人参加竞赛.            作为唯一代表地球,你发

ZOJ 1635 - Directory Listing 解题报告

       题目:1635 Directory Listing(列出目录)        http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=635         看描述好像是chenyue姐姐出的题目.这道题的大意是,给出一个UNIX文件系统的树,以及树节点的自身size,按要求列出它们,保持适当的缩进,并统计每个节点的总的size.输入的第一行代表根结点,每一个节点由name size或者*name size的形式组成(如

ZOJ 1090 - The Circumference of the Circle 解题报告

      题目的链接在这里:       http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1090       题目描述很简单,大意是,给出三个点的坐标,设为A(x1,y1),B (x2, y2),C (x3, y3),然后求出通过这三点的圆的周长(保留两位小数).但推导公式却比较麻烦,我是这样来做的.       首先根据同一个弦的圆心角角度相同,不难得出,圆周的直径d= BC/ sin a = AC/ sin b =

ZOJ 1009, 1115, 1476, 1733, 2405 解题报告

        星期天这天一口气AC了五道题,除了1009外基本都可算是简单题.        (1)1009 Enigma:        http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1009        题目是讲解二战期间德国使用的密码机Enigma.明文通过按键,通过转盘(rotor)转换为密文.如下图所示为有1个转盘,一共有6个字母的情况,每击键一次,转盘转动一格.如果含有多个转盘,则以类似数字进制方式转动,