Tác giả:
Reviewers:
Tiếp nối hai phần trước của series Hình học tính toán, trong bài viết này, ta sẽ tập trung vào các bài toán liên quan đến đa giác, diện tích đa giác và tính chất của các điểm thuộc đa giác.
Các bài viết trước thuộc series Hình học tính toán:
Phần dưới đây liệt kê một số thuật ngữ quen thuộc với đa giác:

Lưu ý: Trong bài viết này, nếu không có chú thích gì thêm, khi nhắc đến "đa giác" chúng ta sẽ hiểu đó là đa giác lồi.
∀i∈[1,n],ai<∑1≤j≤n,j≠iajforall_iin[1, n], a_i<sum_{1leq jleq n, jneq i}a_j ∀i∈[1,n],ai<1≤j≤n,j=i∑aj
Bất đẳng thức cũng có thể được biểu diễn sử dụng cạnh lớn nhất trong đa giác:
2max1≤i≤nai<∑i=1nai2max_{1leq ileq n}a_i < sum_{i = 1}^na_i 21≤i≤nmaxai<i=1∑nai
Để có thể giải quyết các bài toán hình học tính toán một cách hiệu quả, ta nên xây dựng các struct riêng cho từng đối tượng.
Trong bài này, các điểm, vector và đa giác sẽ được cài đặt bằng các CTDL sau:
#include <vector> using namespace std; struct Point{ int x; int y; // Khởi tạo mặc định Point(); // Khởi tạo điểm từ toạ độ Point(int __x, int __y): x(__x), y(__y) {} }; struct Vector { int x; int y; // Khởi tạo mặc định vector Vector(); // Khởi tạo vector từ điểm đầu và điểm cuối Vector(Point p, Point q): x(q.x - p.x), y(q.y - p.y) {} // Tích có hướng của vector hiện tại và một vector khác long long operator ^ (const Vector &v2) const { return 1ll * x * v2.y - 1ll * y * v2.x; } }; struct Polygon { // Số đỉnh thuộc đa giác int nVertices; // Danh sách các đỉnh thuộc đa giác vector <Point> vertices; // Khởi tạo mặc định Polygon(); // Khởi tạo tam giác từ các đỉnh Polygon(Point A, Point B, Point C) : vertices({A, B, C}), nVertices(3) {} // Khởi tạo đa giác từ danh sách đỉnh Polygon(const vector <Point> &__vertices) : vertices(__vertices), nVertices(__vertices.size()) {} // Diện tích, được cài đặt tại phần Diện tích đa giác double area(); // Hai lần diện tích, được cài đặt tại phần Diện tích đa giác long long area2(); };Độ lớn của phần mặt phẳng bị chiếm chỗ bởi một đa giác được gọi là diện tích của đa giác đó. Người ta thường dùng chữ SSS để ký hiệu diện tích, chẳng hạn diện tích ngũ giác ABCDEABCDEABCDE có thể được ký hiệu là SABCDES_{ABCDE}SABCDE.
Bài toán: VNOJ - Diện tích đa giác
Tóm tắt đề bài: Cho đa giác lồi được tạo thành từ nnn điểm cho trước theo đúng thứ tự đó, điểm thứ iii trong số đó có toạ độ là (xi,yi)(x_i, y_i)(xi,yi). Tính diện tích đa giác trên.
Gọi nnn điểm thuộc đa giác theo đúng thứ tự trên là A1,A2,...,AnA_1, A_2, ..., A_nA1,A2,...,An, trong đó A1A_1A1 là điểm có tung độ nhỏ nhất trong số các điểm cùng có hoành độ nhỏ nhất. Để tiện biểu diễn các công thức, trong bài viết này ta sẽ quy ước thêm An+1≡A1A_{n + 1} equiv A_1An+1≡A1.
Các công thức tính diện tích cho tam giác và một số dạng tứ giác đặc biệt vốn đã rất phổ biến. Tuy vậy, gần như chẳng có công thức nào cho phép ta tính ngay diện tích của một đa giác bất kỳ (ngay cả tứ giác đã khó rồi). Chỉ có một cách duy nhất để giải bài toán này: chia hình này thành những hình có thể tính được diện tích.
Nếu bài toán trên xuất hiện trong đề thi vào THPT, với một hình vẽ chẳng có tính chất gì đặc biệt cả, ta đành phải chọn cách chia đơn giản nhất: đường chéo. Tại một đỉnh của đa giác, vẽ các đường chéo đến tất cả các đỉnh không kề với nó: 
Bây giờ, hình đa giác đã chuyển thành một loạt các tam giác. Mà tam giác thì số công thức tính diện tích nhiều vô kể:
Sau khi áp dụng các công thức để tính diện tích tam giác, việc còn lại chỉ là cộng các kết quả lại. Tổng nhận được chắc chắn là diện tích của đa giác cần tính.
Trong các công thức trên, hai công thức đầu ít nhiều phải sử dụng phép căn bậc hai, còn công thức thứ ba với phép toán vector thì đơn giản hơn cả. Khi các đỉnh của đa giác được cho ngược chiều kim đồng hồ, ta có thể bỏ giá trị tuyệt đối của công thức trên:
S=12∑i=2n−1A1Ai→×A1Ai+1→(1)S=frac{1}{2}sum_{i = 2}^{n-1}overrightarrow{A_1A_i}times overrightarrow{A_1A_{i+1}}tag{1} S=21i=2∑n−1A1Ai×A1Ai+1(1)
Đến đây, ta đã có một công thức khá đẹp để tính diện tích đa giác. Tuy nhiên, ta vẫn có thể tiếp tục biến đổi (1)(1)(1) để có công thức đẹp hơn. Ta thấy với i=1i = 1i=1 và i=ni = ni=n, A1Ai→×A1Ai+1→overrightarrow{A_1A_i}times overrightarrow{A_1A_{i+1}}A1Ai×A1Ai+1 nhận giá trị bằng 000. Vì vậy:
S=12∑i=1nA1Ai→×A1Ai+1→=12∑i=1n((xi−x1)(yi+1−y1)−(yi−y1)(xi+1−x1))=12∑i=1n(xiyi+1−xiy1−x1yi+1−xi+1yi+x1yi+xi+1y1)begin{align}S &= frac{1}{2}sum_{i = 1}^{n} overrightarrow{A_1A_i}times overrightarrow{A_1A_{i+1}} &= frac{1}{2}sum_{i = 1}^{n}((x_i-x_1)(y_{i+1}-y_1) - (y_i-y_1)(x_{i+1}-x_1))&=frac{1}{2}sum_{i = 1}^{n}(x_iy_{i+1}-x_iy_1-x_1y_{i+1}-x_{i+1}y_i+x_1y_i+x_{i+1}y_1)tag{2}end{align} S=21i=1∑nA1Ai×A1Ai+1=21i=1∑n((xi−x1)(yi+1−y1)−(yi−y1)(xi+1−x1))=21i=1∑n(xiyi+1−xiy1−x1yi+1−xi+1yi+x1yi+xi+1y1)(2)
Ta áp dụng lại phép chia đa giác và tính diện tích, nhưng lần này với các đỉnh từ A2A_2A2 đến AnA_nAn và thu được các đẳng thức giống như (2)(2)(2). Cộng các kết quả trên lại, ta được:
nS=12∑j=1n∑i=1n(xiyi+1−xiyj−xjyi+1−xi+1yi+xjyi+xi+1yj)=n2∑i=1n(xiyi+1−xi+1yi)+12∑i=1n∑j=1n(−xiyj+xjyi−xjyi+1+xi+1yj)=n2∑i=1n(xiyi+1−xi+1yi)begin{align}nS &= frac{1}{2}sum_{j = 1}^nsum_{i = 1}^n(x_iy_{i+1}-x_iy_j-x_jy_{i+1}-x_{i+1}y_i+x_jy_i+x_{i+1}y_j)&=frac{n}{2}sum_{i = 1}^n(x_iy_{i+1}-x_{i+1}y_i) &quad+ frac{1}{2}sum_{i = 1}^nsum_{j = 1}^n(-x_iy_j + x_jy_i - x_jy_{i+1}+x_{i+1}y_j)&=frac{n}{2}sum_{i = 1}^n(x_iy_{i+1}-x_{i+1}y_i)tag{3}end{align} nS=21j=1∑ni=1∑n(xiyi+1−xiyj−xjyi+1−xi+1yi+xjyi+xi+1yj)=2ni=1∑n(xiyi+1−xi+1yi)+21i=1∑nj=1∑n(−xiyj+xjyi−xjyi+1+xi+1yj)=2ni=1∑n(xiyi+1−xi+1yi)(3)
Suy ra:
S=12∑i=1n(xiyi+1−xi+1yi)(4)S=frac{1}{2}sum_{i = 1}^n{(x_iy_{i+1}-x_{i+1}y_i)}tag{4} S=21i=1∑n(xiyi+1−xi+1yi)(4)
Trong trường hợp các đỉnh được cho theo thứ tự ngược lại, (1)(1)(1) sẽ phải viết lại thành
S=−12∑i=2n−1A1Ai→×A1Ai+1→S=-frac{1}{2}sum_{i = 2}^{n-1}overrightarrow{A_1A_i}times overrightarrow{A_1A_{i+1}} S=−21i=2∑n−1A1Ai×A1Ai+1
Hoàn toàn tương tự, ta biến đổi được thành công thức giống như (4)(4)(4):
S=−12∑i=1n(xiyi+1−xi+1yi)S=-frac{1}{2}sum_{i = 1}^n{(x_iy_{i+1}-x_{i+1}y_i)} S=−21i=1∑n(xiyi+1−xi+1yi)
Như vậy, diện tích của một đa giác với các đỉnh được cho lần lượt theo thứ tự, không phân biệt cùng chiều hay ngược chiều kim đồng hồ là:
S=12∣∑i=1n(xiyi+1−xi+1yi)∣(5)S=frac{1}{2}left|sum_{i = 1}^n{(x_iy_{i+1}-x_{i+1}y_i)}right|tag{5} S=21i=1∑n(xiyi+1−xi+1yi)(5)
Công thức này được gọi là công thức tam giác.
Người ta cũng hay viết (5)(5)(5) thành dạng định thức như sau:
S=12∣det[x1x2...xnx1y1y2...yny1]∣(6)S=frac{1}{2}left|detbegin{bmatrix}x_1 & x_2 & ... & x_n & x_1 y_1 & y_2 & ... & y_n & y_1end{bmatrix}right|tag{6} S=21det[x1y1x2y2......xnynx1y1](6)
Do khi tính định thức, ta lấy tổng của các hiệu giữa hai phần tử chéo nhau, người ta cũng gọi công thức tam giác là công thức Shoelace (Shoelace Formula) (shoelace nghĩa là dây giày).
Công thức tam giác (5)(5)(5) cũng có thể biến đổi một lần nữa thành dạng vector:
S=12∣∑i=1nOAi→×OAi+1→∣(7)S=frac{1}{2}left|sum_{i = 1}^noverrightarrow{OA_i}times overrightarrow{OA_{i + 1}}right|tag{7} S=21i=1∑nOAi×OAi+1(7)
trong đó O(0,0)O(0, 0)O(0,0) là gốc toạ độ.
Bằng một hướng tiếp cận khác, chúng ta cũng có công thức sau:
Định lý: Diện tích đa giác A1A2...AnA_1A_2...A_nA1A2...An với các đỉnh được sắp xếp theo thứ tự cùng chiều hoặc ngược chiều kim đồng hồ là:
S=12∣∑i=1n(xi−xi+1)(yi+yi+1)∣(8)S = frac{1}{2}left|sum_{i = 1}^n(x_i-x_{i+1})(y_i + y_{i+1})right|tag{8} S=21i=1∑n(xi−xi+1)(yi+yi+1)(8)
Công thức (8)(8)(8) được gọi là công thức hình thang (Trapezoid Formula).
Về cơ bản, nếu ta khai triển công thức hình thang, ta vẫn sẽ thu được công thức tam giác. Tuy nhiên, có một cách chứng minh chỉ sử dụng phép chia hình và diện tích của những hình cơ bản, bạn đọc tham khảo bên dưới.
Chứng minh (nhấn để hiện)Xét trường hợp các đỉnh được cho ngược chiều kim đồng hồ. Với mọi AiA_iAi, gọi HiH_iHi là hình chiếu của AiA_iAi lên trục hoành OxOxOx. Khi đó có các tình huống sau xảy ra:
Trường hợp 1: OxOxOx có không quá 1 giao điểm với các cạnh của đa giác

Xét hình thang A1H1H2A2A_1H_1H_2A_2A1H1H2A2 (đây là hình thang vì A1H1A_1H_1A1H1 song song với A2H2A_2H_2A2H2 do cùng vuông góc với OxOxOx). Diện tích hình thang trên là:
SA1H1H2A2=A1H1+A2H22=(x2−x1)(y2+y1)2begin{align}S_{A_1H_1H_2A_2} &=frac{A_1H_1+A_2H_2}{2} &=frac{(x_2 - x_1)(y_2+y_1)}{2}end{align} SA1H1H2A2=2A1H1+A2H2=2(x2−x1)(y2+y1)
Hoàn toàn tương tự, ta tính được diện tích của các hình thang AiHiHi+1Ai+1A_iH_iH_{i+1}A_{i+1}AiHiHi+1Ai+1:
SAiHiHi+1Ai+1=∣xi+1−xi∣(yi+1+yi)2begin{align}S_{A_iH_iH_{i+1}A_{i+1}} &=frac{|x_{i+1}-x_i|(y_{i+1}+y_i)}{2}end{align} SAiHiHi+1Ai+1=2∣xi+1−xi∣(yi+1+yi)
Ta có thể thấy trên đa giác, tồn tại một điểm AkA_kAk nào đó sao cho, xi+1≥xix_{i+1} geq x_ixi+1≥xi với i≤ki leq ki≤k và xi+1≤xix_{i+1} leq x_ixi+1≤xi với i>ki > ki>k, do các điểm đã được sắp theo thứ tự ngược chiều kim đồng hồ. Như vậy, diện tích đa giác đã cho bằng hiệu giữa diện tích đa giác AkAk+1...A1H1HkA_kA_{k+1}...A_1H_1H_kAkAk+1...A1H1Hk (phần phía trên) và đa giác A1A2...AkHkH1A_1A_2...A_kH_kH_1A1A2...AkHkH1 (phần phía dưới). Diện tích của hai đa giác này lại được tính bằng tổng diện tích các hình thang. Như vậy, diện tích đa giác A1A2...AnA_1A_2...A_nA1A2...An được tính như sau:
S=SAkAk+1...A1H1Hk−SA1A2...AkHkH1=∑i=k+1n∣xi−xi+1∣(yi+yi+1)2−∑i=1k∣xi−xi+1∣(yi+yi+1)2=∑i=k+1n(xi−xi+1)(yi+yi+1)2+∑i=1k(xi−xi+1)(yi+yi+1)2=∑i=1n(xi−xi+1)(yi+yi+1)2=12∑i=1n(xi−xi+1)(yi+yi+1)begin{align}S &= S_{A_kA_{k+1}...A_1H_1H_k} - S_{A_1A_2...A_kH_kH_1} &= sum_{i = k + 1}^nfrac{|x_i-x_{i+1}|(y_i + y_{i+1})}{2} - sum_{i = 1}^kfrac{|x_i-x_{i+1}|(y_i + y_{i+1})}{2} &= sum_{i = k + 1}^nfrac{(x_i-x_{i+1})(y_i + y_{i+1})}{2} + sum_{i = 1}^kfrac{(x_i-x_{i+1})(y_i + y_{i+1})}{2} &= sum_{i = 1}^nfrac{(x_i-x_{i+1})(y_i + y_{i+1})}{2} &=frac{1}{2}sum_{i = 1}^n(x_i-x_{i+1})(y_i + y_{i+1})end{align} S=SAkAk+1...A1H1Hk−SA1A2...AkHkH1=i=k+1∑n2∣xi−xi+1∣(yi+yi+1)−i=1∑k2∣xi−xi+1∣(yi+yi+1)=i=k+1∑n2(xi−xi+1)(yi+yi+1)+i=1∑k2(xi−xi+1)(yi+yi+1)=i=1∑n2(xi−xi+1)(yi+yi+1)=21i=1∑n(xi−xi+1)(yi+yi+1)
Trường hợp 2: OxOxOx có 2 giao điểm với các cạnh của đa giác

Giả sử giá trị yiy_iyi nhỏ nhất trong tất cả các điểm AiA_iAi là ζzetaζ. Ta tịnh tiến đa giác theo vector có toạ độ (0,Y)(0, Y)(0,Y), trong đó Y>ζY > zetaY>ζ. Tức là, ta biến đa giác A1A2...AnA_1A_2...A_nA1A2...An thành đa giác B1B2...BnB_1B_2...B_nB1B2...Bn, trong đó BiB_iBi có toạ độ là (xi,yi+Y)(x_i, y_i + Y)(xi,yi+Y). Do Y>ζY > zetaY>ζ, có thể khẳng định mọi giá trị yi+Yy_i + Yyi+Y đều dương. Như vậy, đa giác B1B2...BnB_1B_2...B_nB1B2...Bn không có cạnh nào cắt trục OxOxOx. Theo trường hợp thứ nhất, diện tích của đa giác mới tạo thành là:
S′=12∑i=1n(xi−xi+1)(yi+Y+yi+1+Y)=12×2Y∑i=1n(xi−xi+1)+12∑i=1n(xi−xi+1)(yi+yi+1)=12∑i=1n(xi−xi+1)(yi+yi+1)begin{align}S' &= frac{1}{2}sum_{i = 1}^n(x_i-x_{i+1})(y_i + Y + y_{i+1} + Y)&=frac{1}{2}times2Ysum_{i=1}^n(x_i-x_{i+1}) + frac{1}{2}sum_{i = 1}^n(x_i-x_{i+1})(y_i + y_{i+1})&=frac{1}{2}sum_{i = 1}^n(x_i-x_{i+1})(y_i + y_{i+1})end{align} S′=21i=1∑n(xi−xi+1)(yi+Y+yi+1+Y)=21×2Yi=1∑n(xi−xi+1)+21i=1∑n(xi−xi+1)(yi+yi+1)=21i=1∑n(xi−xi+1)(yi+yi+1)
Ta cũng biết rằng, phép tịnh tiến tạo ra hình mới bằng với hình cũ, và diện tích của chúng là giống nhau. Vậy diện tích hình A1A2...AnA_1A_2...A_nA1A2...An là S=S′S = S'S=S′.
Như vậy, trong cả hai trường hợp, diện tích của đa giác với các đỉnh được cho ngược chiều kim đồng hồ được tính theo công thức sau:
S=12∑i=1n(xi−xi+1)(yi+yi+1)S = frac{1}{2}sum_{i = 1}^n(x_i-x_{i+1})(y_i + y_{i+1}) S=21i=1∑n(xi−xi+1)(yi+yi+1)
Hoàn toàn tương tự, khi các đỉnh được cho cùng chiều kim đồng hồ, diện tích của đa giác là:
S=−12∑i=1n(xi−xi+1)(yi+yi+1)S = -frac{1}{2}sum_{i = 1}^n(x_i-x_{i+1})(y_i + y_{i+1}) S=−21i=1∑n(xi−xi+1)(yi+yi+1)
Hai công thức trên đều cho kết quả không âm. Vậy ta có điều phải chứng minh.
Chú ý: Cách chứng minh trên mô phỏng phép tính tích phân. Sử dụng phép tính tích phân, chứng minh trên sẽ còn đơn giản hơn nữa.
Dưới đây là cài đặt của công thức tam giác (5)(5)(5):
double Polygon::area() { long long s = 0; for (int i = 0; i < nVertices; i ++) { int i1 = (i + 1) % nVertices; s += 1ll * vertices[i].x * vertices[i1].y - 1ll * vertices[i].y * vertices[i1].x; } return abs(1.0 * s / 2); }Nếu nộp thử đoạn code trên, ta sẽ thấy kết quả chưa được đẹp lắm. Về mặt lý thuyết, phương pháp này không có gì sai. Vấn đề nằm ở kiểu dữ liệu double. Đối với kiểu này, các số được biểu diễn dưới dạng dấu phẩy động ±1.????...??‾×10±???‾pmoverline{1.????...??}times 10^{pmoverline{???}}±1.????...??×10±???, bao gồm 1 bit dấu ±pm±, 11 bit cho phần luỹ thừa và 52 bit cho phần sau dấu thập phân. Với số lượng bit để biểu diễn phần thập phân hạn chế như vậy, ta không thể biểu diễn chính xác tất cả các số trên miền giá trị của kết quả. Chẳng hạn, xét tam giác có toạ độ các đỉnh là (−109−1,−109−1)(-10^9 - 1, -10^9 - 1)(−109−1,−109−1), (109+1,−109−1)(10^9 + 1, -10^9 - 1)(109+1,−109−1), (−109−1,109+1)(-10^9 - 1, 10^9 + 1)(−109−1,109+1), diện tích của nó sẽ là 2×1018+4×109+22times 10^{18} + 4times 10^9 + 22×1018+4×109+2. Số này muốn biểu diễn chính xác ở dạng số nguyên phải dùng tới 61 bit (chưa tính dấu), do vậy ta không thể có kết quả đúng tới từng chữ số bằng double được.long double có vẻ cũng là một lựa chọn, tuy nhiên trên phần lớn hệ máy, đó cũng chỉ là double mà thôi.
Để ý thấy hai lần diện tích của đa giác là một số nguyên. Lợi dụng điều đó, với bài toán yêu cầu in ra chính xác diện tích đa giác, ta làm như sau:
long long Polygon::area2() { long long s = 0; for (int i = 0; i < nVertices; i ++) { int i1 = (i + 1) % nVertices; s += 1ll * vertices[i].x * vertices[i1].y - 1ll * vertices[i].y * vertices[i1].x; } return abs(s); } int main() { Polygon plg; // ... auto s = plg.area2(); cout << s / 2 << "." << 5 * (s % 2); return 0; }Nếu bạn thích dùng tích có hướng như công thức (7)(7)(7) thì có thể làm như sau:
long long Polygon::area2() { long long s = 0; Point o(0, 0); for (int i = 0; i < nVertices; i ++) { int i1 = (i + 1) % nVertices; Vector vi(o, vertices[i]); Vector vi1(o, vertices[i1]); s += (vi ^ vi1); } return abs(s); }Rất dễ dàng để nhận ra rằng độ phức tạp về cả không gian và thời gian cần để cài đặt tất cả các công thức đã nói trên là O(n)mathcal{O}(n)O(n)
C.Jordan đã chứng minh rằng: Mọi đa giác không tự cắt đều chia mặt phẳng thành hai miền, một miền bên trong đa giác và một miền bên ngoài đa giác. Như vậy, một điểm có thể nằm ở một trong ba vị trí tương đối sau với một đa giác:
Bài toán: Cho đa giác nnn đỉnh A1A2...AnA_1A_2...A_nA1A2...An không tự cắt, điểm thứ iii có toạ độ (xi,yi)(x_i, y_i)(xi,yi) theo thứ tự ngược chiều kim đồng hồ. Cho mmm điểm P1,P2,...,PmP_1, P_2, ..., P_mP1,P2,...,Pm, với mỗi điểm PjP_jPj hãy kiểm tra xem điểm này nằm trong, nằm trên cạnh hay nằm ngoài đa giác, biết rằng: a) Đa giác đã cho là đa giác lồi b) Đa giác đã cho là đa giác không tự cắt bất kỳ (tức là có thể không lồi)
Để thực hành trường hợp này, bạn đọc tham khảo bài Codeforces - Theodore Roosevelt.

Kiểm tra bằng diện tích là phương pháp đơn giản nhất để xem một điểm có nằm trong đa giác không.
Với mỗi điểm PjP_jPj, nối PjP_jPj với các đỉnh AiA_iAi của tam giác. Ta tính tổng diện tích các tam giác PjAiAi+1P_jA_iA_{i+1}PjAiAi+1. Sẽ có các trường hợp sau xảy ra (xem hình minh hoạ ở trên để hiểu rõ hơn):
Cách kiểm tra này có độ phức tạp không gian là O(n)mathcal{O}(n)O(n), còn độ phức tạp thời gian là O(mn)mathcal{O}(mn)O(mn).
Do đa giác đã cho là đa giác lồi, nên nếu một điểm PjP_jPj nằm trong đa giác, sẽ xảy ra hai trường hợp:

Ta có thể dễ dàng kiểm tra trường hợp thứ hai bằng cách kiểm tra xem hai vector PjA1→overrightarrow{P_jA_1}PjA1 và PjAn→overrightarrow{P_jA_n}PjAn có cùng phương không (hoặc bất kỳ cách nào bạn thích để kiểm tra ba điểm thẳng hàng).
Xét trường hợp thứ nhất. Ta biết tất cả các đỉnh của đa giác được sắp xếp theo thứ tự ngược chiều kim đồng hồ. Do vậy, nếu điểm PjP_jPj nằm bên trong đa giác, sẽ tồn tại một đỉnh AkA_kAk (k≤nk leq nk≤n) của đa giác để PjP_jPj nằm trong góc AkA1Ak+1^widehat{A_kA_1A_{k+1}}AkA1Ak+1. Lúc này, các vector A1Ai→overrightarrow{A_1A_i}A1Ai ứng với các đường chéo từ A1A2A_1A_2A1A2 đến A1AkA_1A_kA1Ak sẽ nằm bên trái hoặc cùng phương với A1Pj→overrightarrow{A_1P_j}A1Pj, và các vector ứng với các đường chéo còn lại nằm bên phải A1Pj→overrightarrow{A_1P_j}A1Pj. Tính chất này làm ta nghĩ ngay đến việc sử dụng chia nhị phân để tìm kiếm điểm AkA_kAk nói trên.
Về kiểm tra tính chất "trái, phải", ta sử dụng phép nhân có hướng vector (xem phần 2 của series này)
Tóm lại, để kiểm tra vị trí tương đối của một điểm PjP_jPj, ta làm như sau:

Cách làm trên có độ phức tạp không gian là O(n)mathcal{O}(n)O(n) và độ phức tạp thời gian là O(n+mlog(n))mathcal{O}(n + mlog(n))O(n+mlog(n)). Nếu được phép xử lý offline và sử dụng hai con trỏ, độ phức tạp không gian và thời gian lần lượt chuyển thành O(n+m)mathcal{O}(n + m)O(n+m) và O(mlogm+n)mathcal{O}(mlog m + n)O(mlogm+n).
Để thực hành trường hợp này, bạn đọc tham khảo bài CSES - Point in Polygon.
Nếu đa giác có "góc lõm", hai phương pháp trên sẽ không sử dụng được nữa.
Đề kiểm tra điểm thuộc miền trong hay ngoài đa giác đơn (không tự cắt), có một thuật toán rất nổi tiếng. Đó là thuật toán chiếu tia (ray casting). Theo đó, từ mỗi điểm PjP_jPj, ta dựng một tia theo một hướng bất kỳ. Sau đó, ta đếm số giao điểm của tia này với các cạnh thuộc đa giác. Nếu số giao điểm là chẵn, điểm này bên ngoài đa giác, ngược lại nó nằm trong đa giác. Trường hợp điểm thuộc cạnh đa giác được xét riêng.

Với dữ kiện đầu vào của bài toán, các đỉnh của đa giác đã được cho theo thứ tự. Như vậy, ta chỉ cần duyệt qua toàn bộ các cặp đỉnh để đểm số giao điểm. Về phần điểm PjP_jPj, ta sẽ sử dụng tia theo phương song song với trục hoành, chiều hướng về chiều dương.
Giả sử ta có tia PjtP_jtPjt. Tia PjtP_jtPjt sẽ cắt cạnh AiAi+1A_iA_{i+1}AiAi+1 nếu PjtP_jtPjt nằm giữa hai đoạn thẳng PjAiP_jA_iPjAi và PjAi+1P_jA_{i+1}PjAi+1. Ta chỉ cần kiểm tra xem PjP_jPj, AiA_iAi và Ai+1A_{i+1}Ai+1 có cùng hướng (theo chiều kim đồng hồ) với thứ tự cho các điểm của đa giác không là được.
Khi chiếu tia PjtP_jtPjt, sẽ có thể xảy ra khả năng tia này đi qua một đỉnh của đa giác. Trong trường hợp này, ta có thể hình dung tia của ta bị dịch lên trên một khoảng rất nhỏ, và do vậy chỉ giao điểm của tia với cạnh nằm phía dưới (ứng với đỉnh có tung độ nhỏ hơn) được xét.
PointPolygonPosition position(Polygon plg, Point p) { bool isIn = false; for (int i = 0; i < plg.nVertices; i++) { int i1 = (i + 1) % plg.nVertices; auto vpi = Vector(p, plg.vertices[i]); auto vpj = Vector(p, plg.vertices[i1]); // Trường hợp nằm trên cạnh if ((vpi ^ vpj) == 0) { int xl = min(plg.vertices[i].x, plg.vertices[i1].x); int xh = max(plg.vertices[i].x, plg.vertices[i1].x); int yl = min(plg.vertices[i].y, plg.vertices[i1].y); int yh = max(plg.vertices[i].y, plg.vertices[i1].y); if (p.x >= xl && p.x <= xh && p.y >= yl && p.y <= yh) { return BOUNDARY; } } // Trường hợp các đỉnh của đa giác ngược chiều kim đồng hồ // Chú ý rằng chỉ có một bên của bất đẳng thức có dấu bằng isIn ^= (plg.vertices[i].y <= p.y && p.y < plg.vertices[i1].y && (vpi ^ vpj) > 0); // Trường hợp các đỉnh của đa giác cùng chiều kim đồng hồ isIn ^= (plg.vertices[i1].y <= p.y && p.y < plg.vertices[i].y && (vpj ^ vpi) > 0); } if (isIn) { return INSIDE; } return OUTSIDE; }Độ phức tạp của cài đặt trên là O(n)mathcal{O}(n)O(n) về không gian và O(mn)mathcal{O}(mn)O(mn) về thời gian.
Bài toán: CSES - Polygon Lattice Points Tóm tắt đề bài: Cho đa giác A1A2...AnA_1A_2...A_nA1A2...An với các đỉnh có toạ độ nguyên cho trước. Đếm số điểm có toạ độ nguyên nằm bên trong và trên các cạnh của đa giác.

Xét đường thẳng đi qua hai điểm AAA và BBB. Phương trình đường thẳng trên có dạng:
y=yA+yB−yAxB−xA(x−xA)y = y_A + frac{y_B-y_A}{x_B-x_A}(x-x_A) y=yA+xB−xAyB−yA(x−xA)
Khi xxx nguyên, để yyy nguyên thì (yB−yA)(x−xA) ⋮ xB−xA(y_B-y_A)(x-x_A) vdots x_B-x_A(yB−yA)(x−xA) ⋮ xB−xA. Để điều này xảy ra thì x−xAx-x_Ax−xA phải là các bội của lcm(yB−yA,xB−xA)xB−xAfrac{text{lcm}(y_B-y_A, x_B-x_A)}{x_B-x_A}xB−xAlcm(yB−yA,xB−xA). Có gcd(yB−yA,xB−xA)gcd(y_B-y_A, x_B-x_A)gcd(yB−yA,xB−xA) giá trị xxx nằm giữa 000 và xB−1x_B - 1xB−1 thoả mãn tính chất trên.
Như vậy, số điểm nguyên nằm trên mỗi cạnh AiAi+1A_iA_{i+1}AiAi+1 (chỉ tính một đầu mút) sẽ là gcd(yi−yi+1,xi−xi+1)gcd(y_i-y_{i+1}, x_i-x_{i+1})gcd(yi−yi+1,xi−xi+1). Cộng các kết quả này lại ta sẽ được số điểm nguyên trên cạnh của đa giác.
Lưu ý: Về mặt định nghĩa, ta vẫn có gcd(a,b)>0gcd(a, b) > 0gcd(a,b)>0 với mọi a,ba, ba,b cùng khác 000. Tuy nhiên, khi cài đặt, kết quả của phép toán có thể là âm nếu ta dùng thuật toán Euclid hoặc hàm __gcd(). Do đó, để chắc chắn có một kết quả dương, ta sẽ sử dụng giá trị gcd(∣yi−yi+1∣,∣xi−xi+1∣)gcd(|y_i-y_{i+1}|, |x_i-x_{i+1}|)gcd(∣yi−yi+1∣,∣xi−xi+1∣)
Định lý Pick: Cho một đa giác không tự cắt với các đỉnh có toạ độ nguyên và diện tích khác không. Gọi diện tích đa giác là SSS, số điểm nguyên nằm bên trong đa giác là III và số điểm nguyên nằm trên cạnh đa giác là BBB. Khi đó diện tích đa giác là:
S=I+B2−1(9)S = I + frac{B}{2}-1tag{9} S=I+2B−1(9)
Do độ dài của bài viết cũng như độ dài của chứng minh, tác giả sẽ không trình bày phần chứng minh trong bài viết này. Bạn đọc tham khảo chứng minh ở đây.
Trong (9)(9)(9), ta đã tính được BBB ở phần trên, và tính được SSS bằng công thức tam giác (5)(5)(5). Ngay lập tức ta suy ra được III.
Tác giả cũng sẽ không trình bày cài đặt cho bài toán này do độ dài của bài viết.
Độ phức tạp không gian cần cho bài toán này là O(n)mathcal{O}(n)O(n). Về thời gian, ta mất O(n)mathcal{O}(n)O(n) để tính diện tích (sử dụng các công thức đã được nói ở trên), O(nlogmaxi∈[1,n](xi,yi))mathcal{O}(nlogmax_{iin[1, n]}(x_i, y_i))O(nlogmaxi∈[1,n](xi,yi)) cho việc đếm số điểm trên cạnh, và O(1)mathcal{O}(1)O(1) để đếm số điểm trong đa giác. Tổng cộng là O(nlogmaxi∈[1,n](xi,yi))mathcal{O}(nlogmax_{iin[1, n]}(x_i, y_i))O(nlogmaxi∈[1,n](xi,yi)).
Link nội dung: https://melodious.edu.vn/cho-da-giac-deu-n-dinh-a95525.html