Time limit: 3.000 seconds
限时:3.000秒
Problem
问题
Given two convex polygons, they may or may not overlap. If they do overlap, they will do so to differing degrees and in different ways. Write a program that will read in the coordinates of the corners of two convex polygons and calculate the 'exclusive or' of the two areas, that is the area that is bounded by exactly one of the polygons. The desired area is shaded in the following diagram:
给定两个可能相互重叠的凸多边形。如果存在重叠,其重叠的角度和方向各异。你要写一个程序读入两个凸多边形各角点的坐标,并计算出二者“异或”得到的区域的面积,也就是在二者全部区域中仅存在其中一个多边形的区域。要求的面积如下图所示:
Input
输入
Input will consist of pairs of lines each containing the number of vertices of the polygon, followed by that many pairs of integers representing the x,y coordinates of the corners in a clockwise direction. All the coordinates will be positive integers less than 100. For each pair of polygons (pair of lines in the data file), your program should print out the desired area correct to two decimal places. The input will end with a line containing a zero (0).
输入由多行组成,每两行一组。每行第一个数字为该多边形顶点的数量,后面是多组整型x, y坐标值,并按顺时针方向排列。所有坐标都为正整数且小于100。对于每两个多边形(输入的每两行数据),你的程序应打印出要求的区域并保留2位小数。输入的最后一行为0。
Output
输出
Output will consist of a single line containing the desired area written as a succession of eight (8) digit fields with two (2) digits after the decimal point. There will not be enough cases to need more than one line.
只有一行输出,将求出的面积按8位数字输出,且小数点后只保留2位(译注:前面不够8位的以空格填充)。不会有太多的测试用例,因此一行就够。
Sample input
输入
3 5 5 8 1 2 3
3 5 5 8 1 2 3
4 1 2 1 4 5 4 5 2
6 6 3 8 2 8 1 4 1 4 2 5 3
0
Sample output
示例输出
△△△△ 0.00 △△△ 13.50
where △ represents a single space.
其中每个小三角代表一个空格。
Analysis
分析
这道题的解题过程非常复杂,要是想从零开始写一个完全正确的程序比较困难。里面牵扯很多算法,还有很多特殊情况,若中间某一步出错则很难为错误定位。因此不建议拿此题练手。
本题主要包括的算法有(均在二维平面上):判断线段相交、求线段交点、求点集的凸包、判断点在多边形内、求多边形面积。我用最简单的一种思路来解决这个问题,用到的都是以前写过的成熟算法。可能有点暴力,但能保证不出错。
首先求得两凸多边形的面积,可按以下公式计算:
然后求得两多边形互在对方内部的点,加入交集顶点数组。这里用判断点在多边形内的算法。首先要保证凸多边形上的点是按顺时针或逆时针方向给出的,对于每一个点(x0, y0),与多边形上的每一条线段vi的起点pi构成向量vsi,若存在vi × vsi < 0(逆时针时为大于号)则点不在多边形内。原理是如果点在其内,那么点依次与多边形上的每个顶点形成的向量和以该顶点为起点的边的夹角必小于180度。
接下来对两多边形的各条线段两两求交,求出所有交点(包括重合的顶点)并加入交集顶点数组。这里用到的算法请见:平面内两条线段的位置关系(相交)判定与交点求解。
再对交集顶点数组求凸包,参见:Graham's Scan法求解凸包问题。其实此时得到的交集顶点已经是凸包了,只是没有按照顺序排列,且可能出现很多重复点,因此用凸包算法再将其整理一遍而已。
最后用两多边形面积之和减去交集面积的二倍即为结果。整个过程在代码的注释中都解释的很清楚了,还是看代码吧。
事实上找出交集所有的顶点还有一个O(n)的算法,就是先找出两个多边形相交的第一个顶点,然后依次向顺时针旋转,添加两多边形的下一个顶点或交点中旋转较多的一点。如此往复,即可一次性得到顺时针排列的交集顶点。但这种算法实现比较复杂,有很多特殊情况需要处理,更重要的是我手头没有成熟的实现,如果贸然将其应用到这种复杂的算法题中,很可能永远也得不到AC,且无法查出错在哪里。
如果您有更好的思路,望不吝赐教。
Solution
解答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | #include <algorithm> #include <iostream> #include <vector> #include <cmath> using namespace std; struct POINTF { float x; float y; }; //保证精度,两个淫点数之差小于0.0001的认为相等 bool Equal( float f1, float f2) { return ( abs (f1 - f2) < 1e-4f); } //判断两点是否相等 bool operator==( const POINTF &p1, const POINTF &p2) { return (Equal(p1.x, p2.x) && Equal(p1.y, p2.y)); } //比较两点坐标大小,先比较x坐标,若相同则比较y坐标 bool operator>( const POINTF &p1, const POINTF &p2) { return (p1.x > p2.x || (Equal(p1.x, p2.x) && p1.y > p2.y)); } //计算两向量外积 float operator^( const POINTF &p1, const POINTF &p2) { return (p1.x * p2.y - p1.y * p2.x); } //判定两线段位置关系,并求出交点(如果存在)。 //有重合:完全重合(6),1个端点重合且共线(5),部分重合(4) //无重合:两端点相交(3),交于线上(2),正交(1),无交(0),参数错误(-1) int Intersection(POINTF p1, POINTF p2, POINTF p3, POINTF p4, POINTF &Int) { //保证参数p1!=p2,p3!=p4 if (p1 == p2 || p3 == p4) { return -1; //返回-1代表至少有一条线段首尾重合,不能构成线段 } //为方便运算,保证各线段的起点在前,终点在后。 if (p1 > p2) { swap(p1, p2); } if (p3 > p4) { swap(p3, p4); } //求出两线段构成的向量 POINTF v1 = {p2.x - p1.x, p2.y - p1.y}, v2 = {p4.x - p3.x, p4.y - p3.y}; //求两向量外积,平行时外积为0 float Corss = v1 ^ v2; //判定两线段是否完全重合 if (p1 == p3 && p2 == p4) { return 6; } //如果起点重合 if (p1 == p3) { Int = p1; //起点重合且共线(平行)返回5;不平行则交于端点,返回3 return (Equal(Corss, 0) ? 5 : 3); } //如果终点重合 if (p2 == p4) { Int = p2; //终点重合且共线(平行)返回5;不平行则交于端点,返回3 return (Equal(Corss, 0) ? 5 : 3); } //如果两线端首尾相连 if (p1 == p4) { Int = p1; return 3; } if (p2 == p3) { Int = p2; return 3; } //经过以上判断,首尾点相重的情况都被排除了 //将线段按起点坐标排序。若线段1的起点较大,则将两线段交换 if (p1 > p3) { swap(p1, p3); swap(p2, p4); //更新原先计算的向量及其外积 swap(v1, v2); Corss = v1 ^ v2; } //处理两线段平行的情况 if (Equal(Corss, 0)) { //做向量v1(p1, p2)和vs(p1,p3)的外积,判定是否共线 POINTF vs = {p3.x - p1.x, p3.y - p1.y}; //外积为0则两平行线段共线,下面判定是否有重合部分 if (Equal(v1 ^ vs, 0)) { //前一条线的终点大于后一条线的起点,则判定存在重合 if (p2 > p3) { Int = p3; return 4; //返回值4代表线段部分重合 } } //若三点不共线,则这两条平行线段必不共线。 //不共线或共线但无重合的平行线均无交点 return 0; } //以下为不平行的情况,先进行快速排斥试验 //x坐标已有序,可直接比较。y坐标要先求两线段的最大和最小值 float ymax1 = p1.y, ymin1 = p2.y, ymax2 = p3.y, ymin2 = p4.y; if (ymax1 < ymin1) { swap(ymax1, ymin1); } if (ymax2 < ymin2) { swap(ymax2, ymin2); } //如果以两线段为对角线的矩形不相交,则无交点 if (p1.x > p4.x || p2.x < p3.x || ymax1 < ymin2 || ymin1 > ymax2) { return 0; } //下面进行跨立试验 POINTF vs1 = {p1.x - p3.x, p1.y - p3.y}, vs2 = {p2.x - p3.x, p2.y - p3.y}; POINTF vt1 = {p3.x - p1.x, p3.y - p1.y}, vt2 = {p4.x - p1.x, p4.y - p1.y}; float s1v2, s2v2, t1v1, t2v1; //根据外积结果判定否交于线上 if (Equal(s1v2 = vs1 ^ v2, 0) && p4 > p1 && p1 > p3) { Int = p1; return 2; } if (Equal(s2v2 = vs2 ^ v2, 0) && p4 > p2 && p2 > p3) { Int = p2; return 2; } if (Equal(t1v1 = vt1 ^ v1, 0) && p2 > p3 && p3 > p1) { Int = p3; return 2; } if (Equal(t2v1 = vt2 ^ v1, 0) && p2 > p4 && p4 > p1) { Int = p4; return 2; } //未交于线上,则判定是否相交 if (s1v2 * s2v2 > 0 || t1v1 * t2v1 > 0) { return 0; } //以下为相交的情况,算法详见文档 //计算二阶行列式的两个常数项 float ConA = p1.x * v1.y - p1.y * v1.x; float ConB = p3.x * v2.y - p3.y * v2.x; //计算行列式D1和D2的值,除以系数行列式的值,得到交点坐标 Int.x = (ConB * v1.x - ConA * v2.x) / Corss; Int.y = (ConB * v1.y - ConA * v2.y) / Corss; //正交返回1 return 1; } // 比较向量中哪个与x轴向量(1, 0)的夹角更大 bool CompareVector( const POINTF &pt1, const POINTF &pt2) { //求向量的模 float m1 = sqrt (pt1.x * pt1.x + pt1.y * pt1.y); float m2 = sqrt (pt2.x * pt2.x + pt2.y * pt2.y); //两个向量分别与(1, 0)求内积 float v1 = pt1.x / m1, v2 = pt2.x / m2; //如果向量夹角相等,则返回离基点较近的一个,保证有序 return (v1 < v2 || v1 == v2 && m1 < m2); } //计算凸包 bool CalcConvexHull(vector<POINTF> &Src) { //点集中至少应有3个点,才能构成多边形 if (Src.size() < 3) { return false ; } //查找基点 vector<POINTF>::iterator i; POINTF ptBase = Src.front(); //将第1个点预设为最小点 for (i = Src.begin() + 1; i != Src.end(); ++i) { //如果当前点的y值小于最小点,或y值相等,x值较小 if (i->y < ptBase.y || (i->y == ptBase.y && i->x > ptBase.x)) { //将当前点作为最小点 ptBase = *i; } } //计算出各点与基点构成的向量 for (i = Src.begin(); i != Src.end();) { //排除与基点相同的点,避免后面的排序计算中出现除0错误 if (*i == ptBase) { i = Src.erase(i); } else { //方向由基点到目标点 i->x -= ptBase.x, i->y -= ptBase.y; ++i; } } //按各向量与横坐标之间的夹角排序 sort(Src.begin(), Src.end(), &CompareVector); //删除相同的向量 Src.erase(unique(Src.begin(), Src.end()), Src.end()); //点集中至少还剩2个点,加上基点才能构成多边形 if (Src.size() < 2) { return false ; } //计算得到首尾依次相联的向量 for (vector<POINTF>::reverse_iterator ri = Src.rbegin(); ri != Src.rend() - 1; ++ri) { vector<POINTF>::reverse_iterator riNext = ri + 1; //向量三角形计算公式 ri->x -= riNext->x, ri->y -= riNext->y; } //依次删除不在凸包上的向量 for (i = Src.begin() + 1; i != Src.end(); ++i) { //回溯删除旋转方向相反的向量,使用外积判断旋转方向 for (vector<POINTF>::iterator iLast = i - 1; iLast != Src.begin();) { float v1 = i->x * iLast->y, v2 = i->y * iLast->x; //如果叉积大于0,则没有逆向旋转 //如果叉积等于0,还需用内积判断方向是否相逆 if (v1 > v2 || (v1 == v2 && i->x * iLast->x > 0 && i->y * iLast->y > 0)) { break ; } //删除前一个向量后,需更新当前向量,与前面的向量首尾相连 //向量三角形计算公式 i->x += iLast->x, i->y += iLast->y; iLast = (i = Src.erase(iLast)) - 1; } } //将所有首尾相连的向量依次累加,换算成坐标 Src.front().x += ptBase.x, Src.front().y += ptBase.y; for (i = Src.begin() + 1; i != Src.end(); ++i) { i->x += (i - 1)->x, i->y += (i - 1)->y; } //添加基点,全部的凸包计算完成 Src.push_back(ptBase); return (Src.size() >= 3); } //计算凸多边形面积 float CalcArea(vector<POINTF> &Covex) { float fArea = 0; vector<POINTF>::iterator i, j; //遍例多边形每一个顶点 for (i = Covex.begin(); i != Covex.end(); ++i) { //j为i的下一个顶点 if ((j = i + 1) == Covex.end()) { j = Covex.begin(); } //累加面积 fArea += j->x * i->y - i->x * j->y; } return fArea / 2.0f; } //判定点是否在多边形内 bool PointInPolygon(POINTF pt, vector<POINTF> &Poly) { //遍例多边形每一个顶点 for ( int i = 0; i < ( int )Poly.size(); ++i) { //j为i的下一个顶点 int j = (i + 1) % ( int )Poly.size(); //构成由给定点到顶点的向量 POINTF p1 = {pt.x - Poly[i].x, pt.y - Poly[i].y}; //构成由当前顶点到下一顶点的向量 POINTF p2 = {Poly[j].x - Poly[i].x, Poly[j].y - Poly[i].y}; //根据外积作出判断 float fCross = p1 ^ p2; if (fCross < 0) { return false ; } } return true ; } //主函数 int main( void ) { vector< float > Result; //循环读入每一组多边形数据 for ( int nNum; cin >> nNum && nNum != 0; ++nNum) { vector<POINTF> Poly1, Poly2; for (POINTF pt; nNum-- > 0 && cin >> pt.x >> pt.y; Poly1.push_back(pt)); cin >> nNum; for (POINTF pt; nNum-- > 0 && cin >> pt.x >> pt.y; Poly2.push_back(pt)); //去掉每个多边形中,相临的重复点 unique(Poly1.begin(), Poly1.end()); unique(Poly2.begin(), Poly2.end()); if (Poly1.size() < 3 || Poly2.size() < 3) { printf ( "%8.2f" , 0.0f); } //计算两多边形的面积和 float fAreaUnion = CalcArea(Poly1); fAreaUnion += CalcArea(Poly2); vector<POINTF> IntPoly; //添加多边形1在多边形2中的点到交集中 for ( int i = 0; i < ( int )Poly1.size(); ++i) { if (PointInPolygon(Poly1[i], Poly2)) { IntPoly.push_back(Poly1[i]); } } //添加多边形2在多边形1中的点到交集中 for ( int i = 0; i < ( int )Poly2.size(); ++i) { if (PointInPolygon(Poly2[i], Poly1)) { IntPoly.push_back(Poly2[i]); } } //求出两多边形所有的交点,含重合的顶点,添加到交集中 for ( int i = 0; i < ( int )Poly1.size(); ++i) { for ( int j = 0; j < ( int )Poly2.size(); ++j) { POINTF Int; int nr = Intersection( Poly1[i], Poly1[(i + 1) % ( int )Poly1.size()], Poly2[j], Poly2[(j + 1) % ( int )Poly2.size()], Int); if (nr == 6) { IntPoly.push_back(Poly1[i]); IntPoly.push_back(Poly1[(i + 1) % ( int )Poly1.size()]); } else if (nr > 0) { IntPoly.push_back(Int); } } } //为交集求凸包,并计算面积 float fIntArea = CalcConvexHull(IntPoly) ? CalcArea(IntPoly) * 2 : 0; //保存结果 Result.push_back(fAreaUnion - fIntArea); } //按格式输出结果 for (vector< float >::iterator i = Result.begin(); i != Result.end(); ++i) { printf ( "%8.2f" , *i); } cout << endl; return 0; } |
![]() |
作者:王雨濛;新浪微博:@吉祥村码农;来源:《程序控》博客 -- http://www.cnblogs.com/devymex/ 此文章版权归作者所有(有特别声明的除外),转载必须注明作者及来源。您不能用于商业目的也不能修改原文内容。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库