文章目录
第二章 二维基础
笔记
-
点:
using Point = pair<db, db>
:自带排序,自由度不高-
推荐
\color{White}\colorbox{Red}{推荐}
推荐
struct Point{db x, y;};
:可承载运算符,成员函数
-
向量:
using Vector = Point;
:或直接用 Piont-
点积 Dot Product: a → ⋅ b → = a x b x + a y b y = ∣ ∣ a → ∣ ∣ ⋅ ∣ ∣ b → ∣ ∣ cos θ \overrightarrow{a}\cdot\overrightarrow{b}=a_xb_x+a_yb_y=||\overrightarrow{a}||\cdot ||\overrightarrow{b}||\cos\theta a⋅b=axbx+ayby=∣∣a∣∣⋅∣∣b∣∣cosθ
应用
- 长度: ∣ ∣ a → ∣ ∣ = a → ⋅ a → ||\overrightarrow{a}||=\sqrt{\overrightarrow{a}\cdot\overrightarrow{a}} ∣∣a∣∣=a⋅a
- 夹角: cos θ = a → ⋅ b → ∣ ∣ a → ∣ ∣ ⋅ ∣ ∣ b → ∣ ∣ \cos\theta=\dfrac{\overrightarrow{a}\cdot\overrightarrow{b}}{||\overrightarrow{a}||\cdot ||\overrightarrow{b}||} cosθ=∣∣a∣∣⋅∣∣b∣∣a⋅b
- 投影: a → cos θ = a → ⋅ b → ∣ ∣ b → ∣ ∣ \overrightarrow{a}\cos\theta=\dfrac{\overrightarrow{a}\cdot \overrightarrow{b}}{||\overrightarrow{b}||} acosθ=∣∣b∣∣a⋅b
- 垂直: a → ⋅ b → = 0 \overrightarrow{a}\cdot \overrightarrow{b}=0 a⋅b=0
-
叉积 Cross product: a → × b → = ( a x b y − a y b x ) k → = ∣ ∣ a → ∣ ∣ ⋅ ∣ ∣ b → ∣ ∣ sin θ k → \overrightarrow{a}\times\overrightarrow{b}=(a_xb_y-a_yb_x)\overrightarrow{k}=||\overrightarrow{a}||\cdot ||\overrightarrow{b}||\sin\theta\overrightarrow{k} a×b=(axby−aybx)k=∣∣a∣∣⋅∣∣b∣∣sinθk
应用
-
平行四边形面积: ∣ ∣ a → ∣ ∣ ⋅ ∣ ∣ b → ∣ ∣ ∣ sin θ ∣ = ∣ ∣ a → × b → ∣ ∣ ||\overrightarrow{a}||\cdot||\overrightarrow{b}|||\sin\theta|=||\overrightarrow{a}\times \overrightarrow{b}|| ∣∣a∣∣⋅∣∣b∣∣∣sinθ∣=∣∣a×b∣∣
-
向量平行: a → × b → = 0 → \overrightarrow{a}\times \overrightarrow{b}=\overrightarrow{0} a×b=0
-
To-Left测试:判断点P在有向直线AB左侧/右侧/上
A B → × A P → { > 0 , P 点 在 左 侧 < 0 , P 点 在 右 侧 = 0 , P 点 在 上 \overrightarrow{AB}\times\overrightarrow{AP}\begin{cases} >0,\quad P点在左侧\\ <0,\quad P点在右侧\\ =0,\quad P点在上\end{cases} AB×AP⎩⎪⎨⎪⎧>0,P点在左侧<0,P点在右侧=0,P点在上
-
-
向量 a → \overrightarrow{a} a 逆时针旋转 θ \theta θ 度:
[ cos θ − sin θ sin θ cos θ ] [ a x a y ] = [ cos θ a x − sin θ a y sin θ a x + cos θ a y ] \begin{bmatrix}\cos\theta&-\sin\theta\\ \sin\theta&\cos\theta \end{bmatrix}\begin{bmatrix}a_x\\ a_y \end{bmatrix}=\begin{bmatrix}\cos\theta a_x-\sin\theta a_y\\ \sin\theta a_x+\cos\theta a_y \end{bmatrix} [cosθsinθ−sinθcosθ][axay]=[cosθax−sinθaysinθax+cosθay]
-
-
线段:
struct Segment {Point a, b;};
记录左右端点- 判断点 P P P 是否在线段 A B AB AB 上: A P → × B P → = 0 , A P → ⋅ B P → ≤ 0 \overrightarrow{AP}\times\overrightarrow{BP}=0,\overrightarrow{AP}\cdot\overrightarrow{BP}\le 0 AP×BP=0,AP⋅BP≤0
- 判断两条线段
A
B
,
C
D
AB,CD
AB,CD 相交:
- 特判:三点共线,四点共线
- 跨立实验:To-Left判断A,B点在直线CD两侧,C,D点在直线AB两侧
-
直线:
struct Line {Point p,v;};
-
表达方法:
- 斜截式 y = k x + b y=kx+b y=kx+b:不能表示垂直y轴
- 截距式 x a + y b = 1 \dfrac{x}{a}+\dfrac{y}{b}=1 ax+by=1:不能表示过原点
- 一般式 A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0:无法体现向量
- 点斜式 y − y 1 = k ( x − x 1 ) y-y_1=k(x-x_1) y−y1=k(x−x1):不能表示垂直y轴
- 两点式 x − x 1 x 2 − x 1 = y − y 1 y 2 − y 1 \dfrac{x-x_1}{x_2-x_1}=\dfrac{y-y_1}{y_2-y_1} x2−x1x−x1=y2−y1y−y1:和线段相似,可用
- 推荐 \color{White}\colorbox{Red}{推荐} 推荐 点向式 ( x , y ) = O P → + λ v → (x,y)=\overrightarrow{OP}+\lambda\overrightarrow{v} (x,y)=OP+λv:直线上一点,方向向量 v → \overrightarrow{v} v。可表示射线和线段
-
计算点 A A A 到直线 ( P , v → ) (P,\overrightarrow{v}) (P,v) 的距离: ∣ ∣ A O → ∣ ∣ = ∣ ∣ P A → ∣ ∣ ⋅ ∣ sin θ ∣ = ∣ ∣ v → × P A → ∣ ∣ ∣ ∣ v → ∣ ∣ ||\overrightarrow{AO}||=||\overrightarrow{PA}||\cdot|\sin\theta|=\dfrac{||\overrightarrow{v}\times\overrightarrow{PA}||}{||\overrightarrow{v}||} ∣∣AO∣∣=∣∣PA∣∣⋅∣sinθ∣=∣∣v∣∣∣∣v×PA∣∣
-
计算点 A A A 到直线 ( P , v → ) (P,\overrightarrow{v}) (P,v) 的投影点 B B B:
O B → = O P → + P B → P B → = ∣ ∣ P B → ∣ ∣ ∣ ∣ v → ∣ ∣ ⋅ v → = P A → ⋅ v → ∣ ∣ v → ∣ ∣ ∣ ∣ v → ∣ ∣ ⋅ v → = P A → ⋅ v → v → 2 ⋅ v → \overrightarrow{OB}=\overrightarrow{OP}+\overrightarrow{PB}\\ \overrightarrow{PB}=\dfrac{||\overrightarrow{PB}||}{||\overrightarrow{v}||}\cdot\overrightarrow{v}=\dfrac{\overrightarrow{PA}\cdot \overrightarrow{v}}{||\overrightarrow{v}||||\overrightarrow{v}||}\cdot\overrightarrow{v}=\dfrac{\overrightarrow{PA}\cdot \overrightarrow{v}}{\overrightarrow{v}^2}\cdot\overrightarrow{v}\\ OB=OP+PBPB=∣∣v∣∣∣∣PB∣∣⋅v=∣∣v∣∣∣∣v∣∣PA⋅v⋅v=v2PA⋅v⋅v -
求两条直线交点 Q Q Q:
O Q → = O P 1 → + P 1 Q → ∵ ∣ ∣ P 1 Q → ∣ ∣ sin α = ∣ ∣ P 1 P 2 → ∣ ∣ sin β , sin α = ∣ ∣ v 2 → × P 2 P 1 → ∣ ∣ ∣ ∣ v 2 → ∣ ∣ ⋅ ∣ ∣ P 2 P 1 → ∣ ∣ , sin β = ∣ ∣ v 1 → × v 2 → ∣ ∣ ∣ ∣ v 1 → ∣ ∣ ⋅ ∣ ∣ v 2 → ∣ ∣ ∴ ∣ ∣ P 1 Q → ∣ ∣ = ∣ ∣ v 2 → × P 2 P 1 → ∣ ∣ ⋅ ∣ ∣ v 1 → ∣ ∣ ∣ ∣ v 1 → × v 2 → ∣ ∣ ∴ O Q → = O P 1 → + ∣ ∣ P 1 Q → ∣ ∣ ∣ ∣ v 1 → ∣ ∣ ⋅ v 1 → = O P 1 → + ∣ ∣ v 2 → × P 2 P 1 → ∣ ∣ ∣ ∣ v 1 → × v 2 → ∣ ∣ ⋅ v 1 → \overrightarrow{OQ}=\overrightarrow{OP_1}+\overrightarrow{P_1Q}\\ \because \dfrac{||\overrightarrow{P_1Q}||}{\sin\alpha}=\dfrac{||\overrightarrow{P_1P_2}||}{\sin\beta},\sin\alpha=\dfrac{||\overrightarrow{v_2}\times \overrightarrow{P_2P_1}||}{||\overrightarrow{v_2}||\cdot||\overrightarrow{P_2P_1}||},\sin\beta=\dfrac{||\overrightarrow{v_1}\times \overrightarrow{v_2}||}{||\overrightarrow{v_1}||\cdot||\overrightarrow{v_2}||}\\ \therefore ||\overrightarrow{P_1Q}||=\dfrac{||\overrightarrow{v_2}\times \overrightarrow{P_2P_1}||\cdot ||\overrightarrow{v_1}||}{||\overrightarrow{v_1}\times \overrightarrow{v_2}||}\\ \therefore \overrightarrow{OQ}=\overrightarrow{OP_1}+\dfrac{||\overrightarrow{P_1Q}||}{||\overrightarrow{v_1}||}\cdot \overrightarrow{v_1}=\color{red}\overrightarrow{OP_1}+\dfrac{||\overrightarrow{v_2}\times \overrightarrow{P_2P_1}||}{||\overrightarrow{v_1}\times \overrightarrow{v_2}||}\cdot \overrightarrow{v_1} OQ=OP1+P1Q∵sinα∣∣P1Q∣∣=sinβ∣∣P1P2∣∣,sinα=∣∣v2∣∣⋅∣∣P2P1∣∣∣∣v2×P2P1∣∣,sinβ=∣∣v1∣∣⋅∣∣v2∣∣∣∣v1×v2∣∣∴∣∣P1Q∣∣=∣∣v1×v2∣∣∣∣v2×P2P1∣∣⋅∣∣v1∣∣∴OQ=OP1+∣∣v1∣∣∣∣P1Q∣∣⋅v1=OP1+∣∣v1×v2∣∣∣∣v2×P2P1∣∣⋅v1
-
-
多边形:
struct Polygon {vector<Point> p;};
按照逆时针顺序,不一点满足凸性,注意从零开始则最后一个点表示时要取余-
求多边形面积: S = 1 2 ∣ ∣ ∑ i = 0 n − 1 O P i → × O P ( i + 1 ) m o d n → ∣ ∣ S=\dfrac{1}{2}\left|\left|\sum\limits_{i=0}^{n-1}\overrightarrow{OP_i}\times \overrightarrow{OP_{(i+1)\mod n}} \right|\right| S=21∣∣∣∣∣∣∣∣i=0∑n−1OPi×OP(i+1)modn∣∣∣∣∣∣∣∣
O O O 在多边形内外均成立
-
判断点是否在多边形内部:
-
凸多边形: n n n 次 To-Left测试
可 O ( log n ) O(\log n) O(logn) 做
-
光线投射算法 Ray casting algorithm:从该点引出一条射线,若这条射线与多边形有奇数个交点,则点在内部,否则点在外部。
-
回转数法 Winding number algorithm:回转数为0则点在外部
回转数:面内闭合曲线逆时针绕过该点的总次数
-
方法一:有精度误差——反三角函数
计算相邻两边夹角的和,注意夹角有方向
-
方法二:无精度误差——Sunday’s algorithm 光线投射法的推广
-
-
边界情况:
- 点在多边形上:特判
- 射线交点为多边形顶点:将该端点视作在射线上侧
-
-
-
圆:
struct Circle {Point c; double r;};
圆心与半径
习题
地址:第一二章习题
B.Winding Number
-
思路:用 winding number 方法判断点在凹多边形内部
- 注意:要首先判断是否在边界上
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define Polygon vector<Point> const int N = 1e4; const db eps = 1e-8; int n, m, is_edge; db xx, yy; int sgn(db x){ if(fabs(x) < eps) return 0; return (x < 0 ? -1 : 1); } //点,向量 struct Point{ db x, y; Point(db x = 0, db y = 0): x(x), y(y){} bool operator == (const Point &a) const {return (sgn(x - a.x) == 0 && sgn(y - a.y) == 0);} Point operator +(const Point &a) const {return Point(x + a.x, y + a.y);} Point operator -(const Point &a) const {return Point(x - a.x, y - a.y);} Point operator *(const db &k) const {return Point(k * x, k * y);} db operator *(const Point &a) const {return x * a.x + y * a.y;} //Dot db operator ^(const Point &a) const {return x * a.y - y * a.x;} //Cross bool operator < (const Point &a) const { if(x == a.x) return y < a.y; return x < a.x; } }; int to_left(Point a, Point b, Point p){ return sgn((b - a) ^ (p - a)); //注意是叉乘 } bool is_point_on_segment(Point a, Point b, Point p){ Point ap = p - a, bp = p - b; if(sgn(ap ^ bp) == 0 && sgn(ap * bp) <= 0) return 1; else return 0; } //直线 struct Line{ Point p, v; Line(Point p = {0, 0}, Point v = {0, 0}): p(p), v(v){} }; //多边形 Polygon pol; int is_point_in_polygon(Point p, Polygon poly){ //判断凹多边形 int wn = 0, n = poly.size(); rep(i, 0, n - 1){ int left_test = to_left(poly[i], poly[(i + 1) % n], p); int d1 = sgn(poly[i].y - p.y), d2 = sgn(poly[(i + 1) % n].y - p.y); if(left_test > 0 && d1 <= 0 && d2 > 0) wn++; //d1 <= 0 和 d2 <= 0为防止射线穿过多边形顶点时计算重复 if(left_test < 0 && d1 > 0 && d2 <= 0) wn--; } return wn; } int main(){ cin >> n; rep(i, 1, n){ cin >> xx >> yy; pol.push_back(Point(xx, yy)); } cin >> m; while(m--){ cin >> xx >> yy; Point tmp = Point(xx, yy); is_edge = 0; rep(i, 0, n - 1){ if(is_point_on_segment(pol[i], pol[(i + 1) % n], tmp)){ is_edge = 1; break; } } if(is_edge){ puts("EDGE"); continue; } cout << is_point_in_polygon(tmp, pol) << endl; } } /* 8 -2 -2 -5 1 -2 4 1 3 -2 0 -3 1 -2 2 1 1 3 -2 1 0 0 1 2 */
C. Mr. Main and Windmills
-
题目地址:Mr. Main and Windmills(2020ICPC昆明)
-
思路:
- 计算出每两个风车的连线和火车路线直线的交点可在火车路线线段上的点,并记录排序。
- 在询问时根据火车行进方向判断第 k k k 个点的位置(若有第 k k k 个点)。
- 注意:判断交点是否在线段内时不需要判断在线段所在直线上了(即 A P → × B P → = 0 \overrightarrow{AP}\times\overrightarrow{BP}=0 AP×BP=0 ),那样当线段太长时误差过大,只需要判断在线段内(即 A P → ⋅ B P → ≤ 0 \overrightarrow{AP}\cdot\overrightarrow{BP}\le 0 AP⋅BP≤0)
//昆明几何 #include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double const int N = 1e3; const db eps = 1e-8; int n, m, h, k; db xs, ys, xt, yt, xx, yy; struct Point{ db x, y; Point(db x = 0, db y = 0): x(x), y(y){} bool operator == (const Point &a) const {return (fabs(x - a.x) <= eps && fabs(y - a.y) <= eps);} Point operator +(const Point &a) const {return Point(x + a.x, y + a.y);} Point operator -(const Point &a) const {return Point(x - a.x, y - a.y);} Point operator *(const db &k) const {return Point(k * x, k * y);} db operator *(const Point &a) const {return x * a.x + y * a.y;} //Dot db operator ^(const Point &a) const {return x * a.y - y * a.x;} //Cross bool operator < (const Point &a) const { if(x == a.x) return y < a.y; return x < a.x; } }; Point start, des, ans, node[N]; vector<Point> inter[N]; struct Line{ Point p, v; Line(Point p = {0, 0}, Point v = {0, 0}): p(p), v(v){} }; Line train; Line get_line(Point a, Point b){ return Line(a, b - a); } Point line_intersection(Line a, Line b){ Point u = a.p - b.p; db t = (b.v ^ u) / (a.v ^ b.v); return a.p + (a.v * t); } int main(){ cin >> n >> m; cin >> xs >> ys >> xt >> yt; start = Point(xs, ys), des = Point(xt, yt); train = get_line(start, des); rep(i, 1, n){ cin >> xx >> yy; node[i] = Point(xx, yy); inter[i].clear(); } rep(i, 1, n){ rep(j, i + 1, n){ //求交点 Point tmp = line_intersection(get_line(node[i], node[j]), train); //判断是否在线段内 if(((tmp - start) * (tmp - des)) <= 0.0) inter[i].push_back(tmp), inter[j].push_back(tmp); } } //排序 rep(i, 1, n) sort(inter[i].begin(), inter[i].end()); while(m--){ cin >> h >> k; int sz = inter[h].size(); if(k > sz){ puts("-1"); continue; } ans = (start < des) ? inter[h][k - 1] : inter[h][sz - k]; printf("%.7lf %.7lf\n", ans.x, ans.y); } } /* 4 2 0 0 5 0 1 3 2 4 4 1 4 5 1 2 3 2 */
D. Airport Construction
- 题目地址:Airport Construction(2017WF)
- 题目:求多边形(可为凹多边形)内部的最长连续线段
- 思路:
O
(
n
4
)
O(n^4)
O(n4)
- 结论:答案的最长线段一定过多边形的两个顶点
- 枚举每两个顶点连成的直线,求出直线与多边形所有边的交点,并排序。依次遍历每一段判断是否在多边形内部。
- 判断一个线段(两端点在多边形边的两条边界直线上)是否在多边形内,可转换为判断线段中点是否在多边形内
E. Operation Love
-
题目地址:Operation Love(2020牛客多校第3场)
-
思路:判断多边形是左右对称中的哪一个,只需要找到基准位置(这里是相邻的边长为 8 , 9 8,9 8,9 的边,因为图中这两条长度的边均只有一条),根据这两边的位置关系判断即可。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double const db eps = 1e-3; //有平方,精度不要开太大 int cas, n, tmp, corner, to9, to8; struct Point{ db x, y; Point(db x, db y): x(x), y(y){} bool operator == (const Point &a) const {return (fabs(x - a.x) <= eps && fabs(y - a.y) <= eps);} Point operator +(const Point &a) const {return Point(x + a.x, y + a.y);} Point operator -(const Point &a) const {return Point(x - a.x, y - a.y);} Point operator *(const int &k) const {return Point(k * x, k * y);} db operator *(const Point &a) const {return x * a.x + y * a.y;} //Dot db operator ^(const Point &a) const {return x * a.y - y * a.x;} //Cross }; int to_left(Point a, Point b){ db t = a ^ b; //注意是叉乘 if(t > eps) return 1; else if(t < -eps) return -1; else return 0; } struct Polygon{ vector<Point> p; }pol; db dis2(Point a, Point b){ Point t = a - b; return t * t; } int main(){ n = 20; //一共20个点 cin >> cas; while(cas--){ pol.p.clear(); rep(i, 0, n - 1){ db tx, ty; cin >> tx >> ty; pol.p.push_back(Point(tx, ty)); } rep(i, 0, n - 1){ //找边长为9的边 if(fabs(dis2(pol.p[i], pol.p[(i + 1) % n]) - 81.0) < eps) tmp = i; } //找边长为8的边 if(fabs(dis2(pol.p[(tmp + 2) % n], pol.p[(tmp + 1) % n]) - 64.0) <= eps){ corner = (tmp + 1) % n; to9 = tmp, to8 = (tmp + 2) % n; } else{ corner = tmp; to9 = (tmp + 1) % n, to8 = (tmp + n - 1) % n; //注意标号 } //确定两条边的位置关系 if(to_left(pol.p[to9] - pol.p[corner], pol.p[to8] - pol.p[corner]) == 1) puts("left"); else puts("right"); } }
F. 三角碰撞(Triangle Collision)
-
题目地址:三角碰撞(Triangle Collision)(2020杭电多校第3场)
-
思路(套路):
- 反射问题套路:将第 k k k 次碰撞转换为第 k k k 次穿过直线(将边框对称反折)
- 二分计算 m i d mid mid 时间的时候穿过的直线是否足够即可
![](https://img-blog.csdnimg.cn/0ad531e5f39b45d6a1ebe5221151bba7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAeHhheXQ=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
//套路:化为直线对称
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
const db eps = 1e-7;
int cas, n;
db L, xx, yy, vx, vy, k, h;
struct Point{
db x, y;
Point(db x, db y): x(x), y(y){}
bool operator == (const Point &a) const {return (fabs(x - a.x) <= eps && fabs(y - a.y) <= eps);}
Point operator +(const Point &a) const {return Point(x + a.x, y + a.y);}
Point operator -(const Point &a) const {return Point(x - a.x, y - a.y);}
Point operator *(const db &k) const {return Point(k * x, k * y);}
db operator *(const Point &a) const {return x * a.x + y * a.y;} //Dot
db operator ^(const Point &a) const {return x * a.y - y * a.x;} //Cross
};
struct Line{
Point p, v;
Line(Point p = {0, 0}, Point v = {0, 0}): p(p), v(v){}
};
Point point_in_line(Line l, db Time){
return l.p + l.v * Time;
}
Line speed;
bool passnode(Point des){
ll coll = 0, t;
db srcz, desz;
//过平行线的次数
if(des.y > 0.0) coll += floor(des.y / h);
else coll += floor(-1.0 * des.y / h) + 1;
//过斜率为 sqrt(3) 的次数
srcz = yy - sqrt(3) * xx, desz = des.y - sqrt(3) * des.x;
t = floor((srcz < desz ? 1.0 : -1.0) * desz / h);
coll += (t + 1) / 2;
//过斜率为 -sqrt(3) 的次数
srcz = yy + sqrt(3) * xx, desz = des.y + sqrt(3) * des.x;
t = floor((srcz < desz ? 1.0 : -1.0) * desz / h);
coll += (t + 1) / 2;
return coll >= k;
}
int main(){
cin >> cas;
while(cas--){
cin >> L >> xx >> yy >> vx >> vy >> k;
h = sqrt(3) / 2.0 * L;
speed = Line(Point(xx, yy), Point(vx, vy));
db l = 0, r = 1e10, mid;
while(r - l > eps){
mid = (l + r) / 2;
if(passnode(point_in_line(speed, mid))) r = mid;
else l = mid;
}
printf("%.8lf\n", l);
}
}
/*
4
4000 0 1732 1000 0 1
4000 0 1732 1000 0 1000000
4000 0 1234 0 -1 1
4000 -1000 1 0 1000 925469
*/
G. Magic Rabbit
-
题目地址:Magic Rabbit(2021江苏省热身赛)
-
思路:
- 引理:两种不同浓度的药水根据不同比例混合后的浓度在两点连成线段之上。(以两种浓度 a , b a,b a,b 为 x , y x,y x,y 轴的二维平面坐标系)
- 于是两个药水的混合在两个点的线段上,再与第三个点混合在线段上某点和第三个点的连线上,也就是三个点围成三角形的内部(含边界)
- 注意三点共线和点重合的情况
-
引理证明:设 V 1 V_1 V1 体积的溶液浓度为 a 1 , b 1 a_1,b_1 a1,b1; V 2 V_2 V2 体积的溶液浓度为 a 2 , b 2 a_2,b_2 a2,b2
则混合后为 V 1 + V 2 V_1+V_2 V1+V2 体积的溶液浓度为 a ′ = V 1 a 1 + V 2 a 2 V 1 + V 2 , b ′ = V 1 b 1 + V 2 b 2 V 1 + V 2 a'=\dfrac{V_1a_1+V_2a_2}{V_1+V_2},b'=\dfrac{V_1b_1+V_2b_2}{V_1+V_2} a′=V1+V2V1a1+V2a2,b′=V1+V2V1b1+V2b2
令 V ′ = V 1 V 1 + V 2 , 则 a ′ = V ′ a 1 + ( 1 − V ′ ) a 2 , b ′ = V ′ b 1 + ( 1 − V ′ ) b 1 即 [ a ′ b ′ ] = V ′ [ a 1 b 1 ] + ( 1 − V ′ ) [ a 2 b 2 ] 令V'=\dfrac{V_1}{V_1+V_2},则a'=V'a_1+(1-V')a_2,b'=V'b_1+(1-V')b_1\\ 即\begin{bmatrix}a'\\b' \end{bmatrix}=V'\begin{bmatrix}a_1\\b_1 \end{bmatrix}+(1-V')\begin{bmatrix}a_2\\b_2 \end{bmatrix} \\ 令V′=V1+V2V1,则a′=V′a1+(1−V′)a2,b′=V′b1+(1−V′)b1即[a′b′]=V′[a1b1]+(1−V′)[a2b2]
其中 V ′ ∈ [ 0 , 1 ] V'\in [0,1] V′∈[0,1],于是点 ( a ′ , b ′ ) (a',b') (a′,b′) 在以点 ( a 1 , b 1 ) , ( a 2 , b 2 ) (a_1,b_1),(a_2,b_2) (a1,b1),(a2,b2) 为顶点的线段上
#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
const db eps = 1e-5; //有平方,精度不要开太大
int cas;
db xx, yy;
struct Point{
db x, y;
Point(db x = 0, db y = 0): x(x), y(y){}
bool operator == (const Point &a) const {return (fabs(x - a.x) <= eps && fabs(y - a.y) <= eps);}
Point operator +(const Point &a) const {return Point(x + a.x, y + a.y);}
Point operator -(const Point &a) const {return Point(x - a.x, y - a.y);}
Point operator *(const db &k) const {return Point(k * x, k * y);}
db operator *(const Point &a) const {return x * a.x + y * a.y;} //Dot
db operator ^(const Point &a) const {return x * a.y - y * a.x;} //Cross
bool operator < (const Point &a) const {
if(x == a.x) return y < a.y;
return x < a.x;
}
};
Point des;
vector<Point> a;
set<Point> st; //去重
set<Point>::iterator iter;
int to_left(Point a, Point b, Point p){
Point ab = b - a, ap = p - a;
db t = (ab ^ ap); //注意是叉乘
if(t > eps) return 1;
else if(t < -eps) return -1;
else return 0;
}
bool is_on_segment(Point a, Point b, Point p){
Point ap = p - a, bp = p - b;
if(fabs(ap ^ bp) <= eps && (ap * bp) <= 0.0) return 1;
else return 0;
}
int main(){
rep(i, 1, 3){
cin >> xx >> yy;
st.insert(Point(xx, yy));
}
for(iter = st.begin(); iter != st.end(); iter++) a.push_back(*iter);
cin >> cas;
while(cas--){
cin >> xx >> yy;
des = Point(xx, yy);
//三点重合
if(a.size() == 1) puts(a[0] == des ? "YES" : "NO");
//两点重合
else if(a.size() == 2) puts(is_on_segment(a[0], a[1], des) ? "YES" : "NO");
else{ //a.size() == 3
int ok = 0;
//在三角形边界上
if(is_on_segment(a[0], a[1], des) || is_on_segment(a[0], a[2], des) || is_on_segment(a[1], a[2], des)) ok = 1;
int left_test = to_left(a[0], a[1], des) + to_left(a[1], a[2], des) + to_left(a[2], a[0], des);
//在三角形内部
if(left_test == 3 || left_test == -3) ok = 1;
puts(ok ? "YES" : "NO");
}
}
}
/*
1 1
3 1
2 3
4
2 1
0 0
2 2
3 3
*/