Time limit: 3.000 seconds
限时:3.000秒
Problem
问题
Consider objects such as these. They are polygons, specified by the coordinates of a centre of mass and their vertices. In the figure, centres of mass are shown as black squares. The vertices will be numbered consecutively anti-clockwise as shown.
观察上图中的三个物体。它们均由多边形表示,并给定其重心和各顶点的坐标。在上图中,重心由黑色方块表示。顶点的编号按逆时针方向依次给出。
An object can be rotated to stand stably if two vertices can be found that can be joined by a straight line that does not intersect the object, and, when this line is horizontal, the centre of mass lies above the line and strictly between its endpoints. There are typically many stable positions and each is defined by one of these lines known as its base line. A base line, and its associated stable position, is identified by the highest numbered vertex touched by that line.
将一个物体的轮廓多边形上某两个顶点连成一条线段,且该线段未穿越多边形的内部,然后通过旋转多边形使该线段达到水平,若重心在这条线段之上且位于线段两端点之间,就认为该物体可以摆放平稳。一般情况下,物体有多个可以放稳的位置,每个稳定位置均由对应的线段(称作“基线”)表示。一条基线会经过至少两个多边形上的点(即端点),基线就由这些点中最大的编号表示。
Write a program that will determine the stable position that has the lowest numbered base line. Thus for the above objects, the desired base lines would be 6 for object 1, 6 for object 2 and 2 for the square. You may assume that the objects are possible, that is they will be represented as non self-intersecting polygons, although they may well be concave.
写一个程序计算出编号最小的基线。对于上图中的物体,“object 1”要求的基线为6,“object 2”为6,“square”为2。你可以假设物体都是规则的,即不存在自相交的轮廓多边形,但有可能是凹多边形。
Input and Output
输入和输出
Successive lines of a data set will contain: a string of less than 20 characters identifying the object; the coordinates of the centre of mass; and the coordinates of successive points terminated by two zeroes (0 0), on one or more lines as necessary. There may be successive data sets (objects). The end of data will be defined by the string '#'.
输入有多行组成,每组数据3行。第一行是不超过20个字符长度的物体标识符,第二行是物体的重心坐标,接下来的一行是物体各顶点坐标,由两个零(0 0)表示结束,没有多余的行。可能会有多组数据集(物体),输入的数据由#号字符串表示结束。
Output will consist of the identification string followed by the number of the relevant base line.
输出也有多行组成,每行前面是物体标识符,后面是对应的基线编号。(译注:按照UVa OJ的管理员在官方论坛里的回复,物体标识符和基线编号之间应有1个或多个空格隔开)
Sample input
输入示例
Object2
4 3
3 2 5 2 6 1 7 1 6 3 4 7 1 1 2 1 0 0
Square
2 2
1 1 3 1 3 3 1 3 0 0
#
Sample output
输出示例
Object2 6
Square 2
Analysis
分析
这道题又使用了求解凸包的算法,详见:Graham's Scan法求解凸包问题。类似的问题前面已经出现至少两次了,见:137 - Polygons (多边形)和109 - SCUD Busters (SCUD重磅炸弹)。
我的解法没有追求效率,但思路比较简明:先求出多边形的凸包,然后根据叉积删除凸包边上多余的点(去除三点共线的情况)。再遍例凸包的每条边,检查是否能满足中心在该边的内侧(外积),且是否在两端点之间(内积)。对能满足条件的边,搜索原多边形在该边上的顶点(包括该边的端点),取最大的一个点的编号作为该边的编号。找出所有边的最小编号即可。
Solution
解答
#include <algorithm> #include <iostream> #include <vector> #include <string> #include <cmath> using namespace std; struct POINT {int x; int y;}; //判断两点是否相等 bool operator==(const POINT &p1, const POINT &p2) { return (p1.x == p2.x && p1.y == p2.y); } //比较两点坐标大小,先比较x坐标,若相同则比较y坐标 bool operator>(const POINT &p1, const POINT &p2) { return (p1.x > p2.x || (p1.x == p2.x && p1.y > p2.y)); } //计算两向量外积 int operator^(const POINT &p1, const POINT &p2) { return (p1.x * p2.y - p1.y * p2.x); } //计算两向量内积 int operator*(const POINT &p1, const POINT &p2) { return (p1.x * p2.x + p1.y * p2.y); } // 比较向量中哪个与x轴向量(1, 0)的夹角更大 bool CompareVector(const POINT &pt1, const POINT &pt2) { //求向量的模 float m1 = sqrt((float)(pt1.x * pt1.x + pt1.y * pt1.y)); float m2 = sqrt((float)(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<POINT> &Src) { //点集中至少应有3个点,才能构成多边形 if (Src.size() < 3) { return false; } //查找基点 vector<POINT>::iterator i; POINT 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<POINT>::reverse_iterator ri = Src.rbegin(); ri != Src.rend() - 1; ++ri) { vector<POINT>::reverse_iterator riNext = ri + 1; //向量三角形计算公式 ri->x -= riNext->x, ri->y -= riNext->y; } //依次删除不在凸包上的向量 for (i = Src.begin() + 1; i != Src.end(); ++i) { //回溯删除旋转方向相反的向量,使用外积判断旋转方向 for (vector<POINT>::iterator iLast = i - 1; iLast != Src.begin();) { int nCross = *i ^ *iLast; //如果叉积小于0,则没有逆向旋转 //如果叉积等于0,还需用内积判断方向是否相逆 if (nCross < 0 || (nCross == 0 && 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); } //主函数 int main(void) { while (true) { string Name; while (Name.empty()) { getline(cin, Name); } if (Name[0] == '#') { break; } POINT Mass; cin >> Mass.x >> Mass.y; vector<POINT> Poly, Convex; for(POINT pt; cin >> pt.x >> pt.y && pt.x != 0 && pt.y != 0;) { Poly.push_back(pt); } Convex = Poly; if (!CalcConvexHull(Convex)) *(int*)0 = 0; //删除删除凸包边上多余的点 for (int i = 0; i < (int)Convex.size(); ++i) { int j = (i + 1) % Convex.size(); int k = (j + 1) % Convex.size(); POINT v1 = {Convex[j].x - Convex[i].x, Convex[j].y - Convex[i].y}; POINT v2 = {Convex[k].x - Convex[j].x, Convex[k].y - Convex[j].y}; //外积为0表示共线,可以删除 if ((v1 ^ v2) == 0) { //删除中间的点 Convex.erase(Convex.begin() + j); --i; //这一次先不将i递增 } } int nMin = Poly.size(); //遍例凸包的每一条边 for (int i = 0; i < (int)Convex.size(); ++i) { int j = (i + 1) % (int)Convex.size(); //构造重心到该边两个顶点的向量 POINT v1 = {Mass.x - Convex[i].x, Mass.y - Convex[i].y}; POINT v2 = {Mass.x - Convex[j].x, Mass.y - Convex[j].y}; POINT s1 = {Convex[j].x - Convex[i].x, Convex[j].y - Convex[i].y}; POINT s2 = {Convex[i].x - Convex[j].x, Convex[i].y - Convex[j].y}; //先作外积,判断重心是否在内侧 //再作内积,判断重心是否在两端点之间 if ((s1 ^ v1) >= 0 && v1 * s1 >= 0 && v2 * s2 >= 0) { int nMax = 0; //查找原多边形中在该边上的点 for (int k = 0; k < (int)Poly.size(); ++k) { //利用外积判断是否点在线上 s2.x = Poly[k].x - Convex[i].x; s2.y = Poly[k].y - Convex[i].y; if ((s2 ^ s1) == 0) { //找出该边的编号(边上所有点编号的最大值) nMax = max(k, nMax); } } //找出编号最小的边 nMin = min(nMin, nMax); } } //输出结果,编号从1开始 cout << Name << ' ' << nMin + 1 << endl; } return 0; }