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 节(线段的性质)。

ZOJ 1010. Area 解题报告相关推荐

  1. zoj 1010 Area (叉积求面积 与 跨立相交实验判断相交)

    题目链接:zoj 1010 题意:给你 N  个点的坐标,点是按照顺序输入的.每一个点都与它后面的那个点连成一条线段,最后一个点与起点相连. 求组成的多边形的面积. 参考博客:https://blog ...

  2. zoj 1010 Area【线段相交问题】

    链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1010 http://acm.hust.edu.cn/vjudge/ ...

  3. BJFU 1010 博弈 解题报告

    有两堆扑克牌,数量任意(均大于0),可以不同.两人开始博弈,轮流抽取扑克牌.规定每次取牌有两种方式,第一种方式是任选一堆扑克牌,并从中取走任意张(大于0):第二种是在两堆扑克牌中同时取走相同张数(大于 ...

  4. [zz][ZOJ Monthly]October 2008解题报告

    Connect4 Connect Four(Author: SONG, Yu[EZdestroyer]) 题目的背景就是Linux下的同名游戏,两个人在7*7的槽里轮流扔棋子,每次棋子都扔进某一列,棋 ...

  5. 解题报告(十八)数论题目泛做(Codeforces 难度:2000 ~ 3000 + )

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一 ...

  6. 训练指南第一部分解题报告

    主要是提供训练指南第一部分解题报告链接,后面会持续更新中 307 - Sticks  (DFS+剪枝) 11292 - Dragon of Loowater (贪心) 11729 - Commando ...

  7. cf1206解题报告

    目录 cf1206解题报告 A B C D E,F cf1206解题报告 A 模拟 #include <bits/stdc++.h> using namespace std; int n, ...

  8. Codeforces Round #697 (Div. 3)A~G解题报告

    Codeforces Round #697 (Div. 3)A~G解题报告 题 A Odd Divisor 题目介绍 解题思路 乍一想本题,感觉有点迷迷糊糊,但是证难则反,直接考虑没有奇数因子的情况, ...

  9. Tarjan算法求解桥和边双连通分量(附POJ 3352 Road Construction解题报告)

    http://blog.csdn.net/geniusluzh/article/details/6619575 在说Tarjan算法解决桥和边双连通分量问题之前我们先来回顾一下Tarjan算法是如何求 ...

  10. 【解题报告】2015ACM/ICPC亚洲区上海站

    题目链接 A.An Easy Physics Problem(HDU 5572) 思路 我们可以将问题分为以下 22 种情况: 球与圆柱相撞 球与圆柱不相撞 我们可以通过判断以 AA 为起点以 V⃗  ...

最新文章

  1. android studil打断点_Android Studio你不知道的调试技巧
  2. 海拨3000点位的岛型堰塞湖
  3. [Qt教程] 第48篇 进阶(八) 3D绘图简介
  4. 【Docker】Asp.net core在docker容器中的端口问题
  5. 设置MongoDB副本集分为4个步骤
  6. 修改value_Python | 快速修改或命名N个文件夹名称,你会吗?
  7. 怎么上传文件到kk服务器,VS Code 关于SFTP上传文件到多服务器的配置
  8. 修改oracle数据连接数据库,如何修改oracle数据库的连接数
  9. OSPF Packet Details
  10. geotools 读取shp属性过滤_Flink进阶之使用布隆过滤器实现UV统计
  11. 解决RecyclerView的onClickListener问题
  12. mysql架设手游_魔天屠龙传手游私服架设源码+大中控后台+搭建教程
  13. 红外遥控(NEC协议)编程
  14. Linux_29_Linux-Vsftpd
  15. 如何判断一个APP页面是原生的还是H5页面
  16. tomcat+nginx配置htpps
  17. SpringMVC中使用hibernate-validator的坑
  18. 查询显卡状态 Tesla M40
  19. c语言中1 lt lt 10什么意思,卡西欧lt1和lt3是什么意思
  20. 爱丁堡 ANLP-Lecture 1(NLP Structure Morphology, Ambiguity, Part of Speech)

热门文章

  1. PAIP.vs2010打开某个FORM窗体设计视图时崩溃时
  2. paip.提升用户体验与提升安全性----记住密码
  3. “超级买手”阿里:一年投资65笔,涉及金额高达5400亿
  4. 堪比黑石 花旗、摩根大通的股东 揭开贝莱德的神秘面纱
  5. 他们才是全球高频交易顶级玩家
  6. 阿里云资深专家起底云网络平台的技术架构升级之路-洛神3.0
  7. 我的 HTTP/1.1 好慢啊!
  8. 郑州计算机五年大专学校排名,河南郑州大专院校最新排名2019,盘点河南省5所最好专科学校名单...
  9. 【通信仿真】基于matlab多域网络仿真【含Matlab源码 1794期】
  10. 【产量预测】基于matlab BP和GRNN神经网络预测粮食产量【含Matlab源码 1247期】