题目:传送门
题意:铁人三项比赛,给你 n 个参赛者在每一项比赛的速度 a[ i ] ,b[ i ], c[ i ],输出 n 行,第 i 行代表是否能通过改变三项比赛的路程,使得第 i 位参赛者是第一个到达终点的(唯一一个到达终点的)。
1 <= n <= 100, 1 <= ai, bi, ci <= 10000
思路:
这题是半平面交的应用,一开始怎么也没想到可以转化为求解不等式方程组的问题,这题还是挺巧妙的。
我是看这篇博客学习的 -> 戳
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> #include <queue> #include <map> #include <vector> #include <set> #include <string> #include <math.h> #define LL long long #define mem(i, j) memset(i, j, sizeof(i)) #define rep(i, j, k) for(int i = j; i <= k; i++) #define dep(i, j, k) for(int i = k; i >= j; i--) #define pb push_back #define make make_pair #define INF INT_MAX #define inf LLONG_MAX #define PI acos(-1) #define fir first #define sec second using namespace std; const int N = 150; const double eps = 1e-10; const double maxL = 1e10; struct Point { double x, y; Point(double x = 0, double y = 0) : x(x), y(y) { } }; int dcmp(double x) { if(fabs(x) < eps) return 0; else return x < 0 ? -1 : 1; } Point operator + (Point A, Point B) { return Point(A.x + B.x, A.y + B.y); } Point operator - (Point A, Point B) { return Point(A.x - B.x, A.y - B.y); } Point operator * (Point A, double p) { return Point(A.x * p, A.y * p); } Point operator / (Point A, double p) { return Point(A.x / p, A.y / p); } double Cross(Point A, Point B) { return A.x * B.y - A.y * B.x; } double Dot(Point A, Point B) { return A.x * B.x + A.y * B.y; } double Length(Point A) { return sqrt(Dot(A, A)); } /* 有向直线,它的左边就是对应的半平面 */ struct Line { Point p; /// 直线任意一点 Point v; /// 方向向量 double ang; /// 极角,即从x正半轴旋转到向量v所需要的角(弧度) Line() { } Line(Point p, Point v) : p(p), v(v) { ang = atan2(v.y, v.x); } bool operator < (const Line& L) const { return ang < L.ang; } }; /* 点p在有向直线L的左边 */ bool OnLeft(Line L, Point p) { return dcmp(Cross(L.v, p - L.p)) > 0; } /* 二直线交点,假设交点唯一存在。*/ Point GLI(Line a, Line b) { Point u = a.p - b.p; double t = Cross(b.v, u) / Cross(a.v, b.v); return a.p + a.v * t; } int HPI(Line* L, int n, Point* Q) { sort(L, L + n); /// 极角排序 int st, ed; /// 双端队列的第一个元素和最后一个元素的下标 Point *p = new Point[n]; /// p[i]为q[i]和q[i+1]的交点 Line *q = new Line[n]; /// 双端队列 q[st = ed = 0] = L[0]; rep(i, 1, n - 1) { while(st < ed && !OnLeft(L[i], p[ed - 1])) ed--; while(st < ed && !OnLeft(L[i], p[st])) st++; q[++ed] = L[i]; /// 平行取内测那条 if(fabs(Cross(q[ed].v, q[ed - 1].v)) < eps) { ed--; if(OnLeft(q[ed], L[i].p)) q[ed] = L[i]; } if(st < ed) p[ed - 1] = GLI(q[ed - 1], q[ed]); } while(st < ed && !OnLeft(q[st], p[ed - 1])) ed--; if(ed - st <= 1) return 0; p[ed] = GLI(q[ed], q[st]); int m = 0; rep(i, st, ed) Q[m++] = p[i]; return m; } Point P[N], Q[N],tmpa, tmpb; Line L[N]; int n; double a[N], b[N], c[N], A, B, C; bool judge(int x) { int cnt = 4; ///给半平面加一个框,这样可以使解x,y都大于0,也可以避免所有半平面交起来后为不为凸多边形,而是一个敞开的区域 ///如果题目输入的不是一个多边形,而是本题这种输入若干不等式组的情况,这样的限定就是必须的,不然有bug,例如,两条线是平行的(但是极角不同), ///极角排序后又挨在一起, 那么就可能求它们的交点,就容易出错 tmpa.x = 0; tmpa.y = 0; tmpb.x = maxL; tmpb.y = 0; L[0] = Line(tmpa, tmpb - tmpa); tmpa = tmpb; tmpb.x = maxL; tmpb.y = maxL; L[1] = Line(tmpa, tmpb - tmpa); tmpa = tmpb; tmpb.x = 0; L[2] = Line(tmpa, tmpb - tmpa); tmpa = tmpb; tmpb.y = 0; L[3] = Line(tmpa, tmpb - tmpa); rep(i, 1, n) { if(i == x) continue; A = 1.0 / a[i] - 1.0 / a[x]; B = 1.0 / b[i] - 1.0 / b[x]; C = 1.0 / c[i] - 1.0 / c[x]; int d1 = dcmp(A), d2 = dcmp(B), d3 = dcmp(C); /* 下面是根据a*x+b*y+c>0取向量p1p2, 其中p1(x1,y1),p2(x2,y2) 就是将直线转化为以两点的表示,取向量p1p2左半为半平面 */ if(!d1) { if(!d2) { if(d3 <= 0) return false; continue; } tmpa.x = 0; tmpb.x = d2; tmpa.y = tmpb.y = -C / B; } else { if(!d2) { tmpa.x = tmpb.x = -C / A; tmpa.y = 0; tmpb.y = -d1; } else { tmpa.x = 0; tmpa.y = -C / B; tmpb.x = d2; tmpb.y = -(C + A * tmpb.x) / B; } } L[cnt++] = Line(tmpa, tmpb - tmpa); } if(HPI(L, cnt, Q) == 0) return false; else return true; } void solve() { scanf("%d", &n); rep(i, 1, n) scanf("%lf %lf %lf", &a[i], &b[i], &c[i]); rep(i, 1, n) { if(judge(i)) puts("Yes"); else puts("No"); } } int main() { // int _; scanf("%d", &_); // while(_--) solve(); solve(); return 0; }