ICPC Regional Hanoi 2024 Part 4 of 4
ICPC Regional Hanoi 2024 Editorial - A
Category: Challenger Tags: competitive-programming icpc
Lời giải bài hình chống full.
A: Approaching Hurricane
Đề bài
Cho một đa giác đơn không tự cắt gồm đỉnh trên mặt phẳng 2D. Có truy vấn độc lập. Mỗi truy vấn mô tả một cơn bão là một hình tròn di chuyển từ tâm với bán kính đến tâm với bán kính theo đường thẳng:
Bán kính biến thiên tuyến tính theo khoảng cách di chuyển giữa và .
Miền bị ảnh hưởng bởi cơn bão là tổng thể diện tích được bao phủ bởi hình tròn tại mọi thời điểm trong suốt hành trình di chuyển từ đến (về mặt hình học, đây là hợp của hai hình tròn tại và hình thang cân nối các tiếp tuyến chung ngoài của hai đường tròn này).
Với mỗi truy vấn, tính diện tích phần giao giữa đa giác đảo và miền ảnh hưởng của cơn bão.
Giới hạn: số test , , tọa độ có trị tuyệt đối không quá , , tổng trên mọi test không vượt quá . Đáp án được chấp nhận nếu sai số tuyệt đối hoặc tương đối không vượt quá .
Lời giải
Trong kì thi chính thức không có đội nào AC bài toán này.
Ý tưởng chính
Do tổng trên mọi test không vượt quá , với mỗi truy vấn ta có thể duyệt qua toàn bộ cạnh của đa giác. Bài toán có hai khó khăn chính: đa giác có thể không lồi, và biên của miền có những đoạn cong. Lời giải lần lượt xử lý hai khó khăn này qua bốn bước:
Ở Bước 1, ta biểu diễn đa giác thành tổng có dấu của tam giác, mỗi tam giác ứng với một cạnh của đa giác. Nhờ đó, bài toán quy về việc tính diện tích phần giao giữa một tam giác và miền .
Ở Bước 2, ta chứng minh là bao lồi của hai hình tròn lúc đầu và lúc cuối, sau đó chia thành ba mảnh đơn giản: hai phần hình tròn và một hình thang.
Ở Bước 3, ta cắt mỗi tam giác theo ba mảnh nói trên. Phần nằm trong hình thang là một đa giác lồi, nên diện tích của phần này được tính bằng công thức shoelace.
Ở Bước 4, ta tính diện tích phần giao giữa một đa giác lồi và một hình tròn, áp dụng cho hai phần còn lại.
Trong lời giải, ta dùng các ký hiệu sau: là diện tích có hướng của hình ; là tích có hướng của hai vector và ; là hình tròn tâm bán kính và là hình tròn tâm bán kính , tức là hình tròn lúc cơn bão bắt đầu và lúc cơn bão kết thúc.
Bước 1: Biểu diễn đa giác thành tổng có dấu của các tam giác
Ta chọn một điểm bất kỳ làm điểm gốc (chẳng hạn trung điểm của ). Với mỗi cạnh của đa giác (quy ước ), ta xét tam giác cùng với dấu của tam giác đó: tam giác mang dấu dương nếu các đỉnh , , được sắp theo chiều ngược chiều kim đồng hồ, và mang dấu âm trong trường hợp ngược lại. Công thức shoelace quen thuộc chính là tổng diện tích có hướng của tam giác này. Tuy nhiên, để xử lý được phần giao với , ta cần một tính chất mạnh hơn, phát biểu cho từng điểm của mặt phẳng.
Nhận xét. Với mỗi điểm không nằm trên cạnh của tam giác nào, gọi là tổng dấu của các tam giác chứa : mỗi tam giác dương chứa được tính , mỗi tam giác âm chứa được tính . Nếu đa giác được cho theo chiều ngược chiều kim đồng hồ thì với mọi điểm nằm trong đa giác, và với mọi điểm nằm ngoài đa giác. Nếu đa giác được cho theo chiều kim đồng hồ thì giá trị ứng với các điểm nằm trong đa giác là .
Chứng minh. Gọi là tia xuất phát từ và có cùng hướng với vector , tức là tia đi ra xa điểm . Điểm nằm trong tam giác khi và chỉ khi đường thẳng cắt cạnh tại một điểm nằm xa hơn , tức là khi và chỉ khi tia cắt cạnh . Ngoài ra, dấu của tam giác cho biết cạnh , đi theo chiều từ đến , cắt tia từ bên phải sang bên trái hay từ bên trái sang bên phải. Do đó bằng số lần biên đa giác cắt tia theo chiều này trừ đi số lần cắt theo chiều kia.
Bây giờ ta đi dọc tia từ ra xa vô cùng. Mỗi lần tia cắt biên, ta đi từ trong đa giác ra ngoài hoặc từ ngoài vào trong. Vì phần trong của đa giác luôn nằm về cùng một phía của các cạnh (bên trái nếu đa giác được cho ngược chiều kim đồng hồ), hai loại lần cắt này ứng với hai chiều cắt ngược nhau. Đầu xa của tia nằm ngoài đa giác. Nếu nằm ngoài đa giác, số lần đi vào bằng số lần đi ra, nên . Nếu nằm trong đa giác, số lần đi ra nhiều hơn số lần đi vào đúng một lần, nên . Đây cũng chính là cơ sở của thuật toán kiểm tra một điểm có nằm trong đa giác hay không bằng cách bắn tia.
Theo nhận xét trên, khi cộng diện tích có hướng của các phần , mỗi điểm thuộc được tính đúng lần: các điểm nằm ngoài đa giác bị triệt tiêu, còn mỗi điểm nằm trong đa giác được tính đúng một lần, với dấu phụ thuộc vào chiều của đa giác. Do đó:
Vì đa giác có thể được cho theo chiều kim đồng hồ, đáp án của truy vấn là trị tuyệt đối của tổng trên. Như vậy, bài toán quy về việc tính diện tích phần giao giữa một tam giác và miền .
Bước 2: Hình dạng của miền ảnh hưởng
Đặt và . Khi tâm cơn bão đã đi được một tỉ lệ của quãng đường , khoảng cách từ tâm đến và đến lần lượt là và . Theo công thức trong đề bài, tâm cơn bão lúc đó là điểm , và bán kính là
Như vậy, cả tâm và bán kính đều thay đổi tuyến tính theo . Gọi là hình tròn tại thời điểm đó. Sau khi tâm tới , cơn bão đứng yên nên không phủ thêm điểm nào, do đó là hợp của các hình tròn với .
Nhận xét. Miền là bao lồi của .
Chứng minh. Trước hết, ta chứng minh mỗi hình tròn gồm đúng các điểm có dạng với và .
Nếu và , khoảng cách từ điểm đến tâm bằng . Theo bất đẳng thức tam giác, đại lượng này không vượt quá , nên điểm đang xét thuộc .
Ngược lại, mỗi điểm của có dạng với , trong đó là bán kính của . Đặt và . Khi đó , , và .
Lấy hợp theo mọi , ta được là tập các điểm với , và . Đây chính là bao lồi của hợp hai tập lồi và .
Nếu , một hình tròn nằm trọn trong hình tròn kia, nên chính là hình tròn lớn hơn. Trường hợp này được xử lý riêng ở phần Cài đặt. Trong phần còn lại của Bước 2 và Bước 3, ta giả sử . Khi đó, bao lồi của hai hình tròn được giới hạn bởi hai tiếp tuyến chung ngoài và hai cung tròn ở hai đầu (Hình 2). Có thể hình dung trực quan như sau: khoảng cách có hướng từ một điểm đến một đường thẳng là hàm bậc nhất theo tọa độ của điểm, nên khoảng cách từ tâm của đến mỗi tiếp tuyến chung ngoài thay đổi tuyến tính theo , bằng khi và bằng khi . Vì vậy khoảng cách này luôn bằng bán kính của , tức là mọi hình tròn trung gian đều tiếp xúc với cả hai tiếp tuyến chung ngoài.
Để thuận tiện cho việc tính toán, ta đổi hệ tọa độ: gốc tọa độ là trung điểm của đoạn , trục cùng hướng với vector . Trong hệ tọa độ mới, và .
Ta tìm tiếp tuyến chung ngoài phía trên dưới dạng , trong đó là vector pháp tuyến đơn vị hướng ra phía ngoài . Với một điểm nằm cùng phía với so với đường thẳng này, khoảng cách từ đến đường thẳng bằng . Đường thẳng tiếp xúc với và khi và chỉ khi khoảng cách từ và đến đường thẳng lần lượt bằng và :
Trừ vế theo vế hai đẳng thức, ta được . Vì , đẳng thức này cho . Do là vector đơn vị hướng lên trên, với . Thay vào đẳng thức thứ nhất, ta tìm được hằng số :
Do tính đối xứng qua trục , tiếp tuyến chung ngoài phía dưới có phương trình , với và cùng hằng số .
Tiếp điểm của một đường tròn với một tiếp tuyến là chân đường vuông góc hạ từ tâm xuống tiếp tuyến đó. Vì vậy, hai tiếp điểm trên là và , còn hai tiếp điểm trên là và . Hai vector và có cùng hoành độ, nên hai tiếp điểm trên cùng một đường tròn nằm trên cùng một đường thẳng đứng. Hoành độ của hai đường thẳng đứng này lần lượt là
và ta có . Hai đường thẳng đứng và chia thành ba mảnh (Hình 2):
Trong nửa mặt phẳng , biên của là cung tròn của nối hai tiếp điểm. Do đó, phần của nằm trong nửa mặt phẳng này trùng với phần của nằm trong nửa mặt phẳng đó.
Trong dải , biên của gồm hai đoạn tiếp tuyến. Do đó, phần của nằm trong dải này là một hình thang cân, chính là giao của bốn nửa mặt phẳng , , và .
Tương tự, trong nửa mặt phẳng , phần của trùng với phần của .
Bước 3: Cắt một tam giác theo ba mảnh của miền ảnh hưởng
Xét một tam giác ở Bước 1. Ta ký hiệu:
là phần của tam giác nằm trong nửa mặt phẳng ;
là phần của tam giác nằm trong hình thang ở Bước 2;
là phần của tam giác nằm trong nửa mặt phẳng .
Theo cách chia ở Bước 2, phần chung của và chính là phần chung của và ; tương tự, phần chung của và là phần chung của và ; còn nằm trọn trong . Do đó:
Số hạng ở giữa là diện tích của một đa giác lồi, được tính bằng công thức shoelace. Hai số hạng còn lại là diện tích phần giao giữa một đa giác lồi và một hình tròn, sẽ được tính ở Bước 4.
Cả ba đa giác , và đều thu được bằng cách cắt một đa giác lồi bởi một nửa mặt phẳng, lặp lại một số lần: và cần một lần cắt, còn cần bốn lần cắt, ứng với bốn nửa mặt phẳng tạo nên hình thang. Thao tác cắt này được thực hiện bằng thuật toán Sutherland–Hodgman (hàm clip_halfplane trong code), được trình bày chi tiết trong khung bên dưới. Mỗi lần cắt cho ra một đa giác lồi có cùng chiều với đa giác ban đầu, nên dấu của diện tích có hướng được giữ nguyên; đồng thời, số đỉnh tăng thêm không quá một. Vì vậy, có không quá đỉnh.
Thuật toán Sutherland–Hodgman
Bài toán. Cho đa giác lồi , với các đỉnh được liệt kê theo thứ tự trên biên, và nửa mặt phẳng . Ta cần tìm đa giác là phần chung của và nửa mặt phẳng này.
Phân loại điểm. Đặt . Điểm nằm trong nửa mặt phẳng nếu , và nằm ngoài nếu . Vì là hàm bậc nhất, trên một cạnh giá trị của thay đổi tuyến tính từ đến . Do đó, cạnh cắt đường thẳng biên khi và chỉ khi hai đầu mút và nằm ở hai phía khác nhau.
Thuật toán. Ta đi một vòng quanh biên của , lần lượt xét các cạnh , , …, . Với mỗi cạnh , ta ghi các điểm sau vào đa giác kết quả, theo đúng thứ tự:
Nếu nằm trong, ta ghi điểm .
Nếu và nằm ở hai phía khác nhau, ta ghi giao điểm của cạnh với đường thẳng biên.
Giao điểm có dạng với . Vì , giao điểm ứng với giá trị
Ví dụ. Hình 4 minh họa việc cắt tứ giác , trong đó và nằm trong nửa mặt phẳng, còn và nằm ngoài. Thuật toán xét bốn cạnh như sau:
| Cạnh | Đầu cạnh | Cuối cạnh | Các điểm được ghi |
|---|---|---|---|
| trong | trong | ||
| trong | ngoài | , sau đó giao điểm | |
| ngoài | ngoài | không có | |
| ngoài | trong | giao điểm |
Kết quả là tứ giác . Các đỉnh nằm trong ( và ) được giữ nguyên theo thứ tự cũ, còn phần biên nằm ngoài () được thay bằng đoạn thẳng nằm trên đường thẳng biên.
Tính đúng đắn. Phần chung của và nửa mặt phẳng là giao của hai tập lồi, nên cũng là một đa giác lồi. Nếu mọi đỉnh của nằm trong nửa mặt phẳng, kết quả là toàn bộ ; nếu mọi đỉnh nằm ngoài, kết quả là tập rỗng; thuật toán cho đúng kết quả trong cả hai trường hợp này. Trong trường hợp còn lại, đường thẳng biên cắt biên của tại đúng hai điểm và chia biên của thành hai đường gấp khúc: một đường nằm trong nửa mặt phẳng và một đường nằm ngoài. Biên của đa giác kết quả gồm đường gấp khúc nằm trong, được khép lại bởi đoạn thẳng nối hai giao điểm. Khi đi một vòng quanh , thuật toán ghi lại đúng các đỉnh của đường gấp khúc nằm trong, cùng với hai giao điểm ở hai đầu, theo thứ tự xuất hiện trên biên. Vì vậy, đa giác kết quả có cùng chiều với . Hơn nữa, có ít nhất một đỉnh của bị loại và chỉ có hai giao điểm được thêm vào, nên đa giác kết quả có không quá đỉnh.
Để cắt một đa giác bởi nhiều nửa mặt phẳng, ta áp dụng thuật toán lần lượt với từng nửa mặt phẳng. Hàm clip_halfplane trong code cài đặt đúng thuật toán trên, kèm theo hai chi tiết để tránh sai số: một điểm nằm ngoài nhưng rất gần đường thẳng biên (sai lệch cỡ , tính tương đối theo độ lớn của các giá trị) vẫn được coi là nằm trong, và tham số được giới hạn trong đoạn .
Bước 4: Diện tích giao giữa đa giác lồi và hình tròn
Cho đa giác lồi và hình tròn có tâm , bán kính . Nhận xét ở Bước 1 đúng với mọi điểm gốc và mọi hình được đem giao, nên ta có thể áp dụng nhận xét đó với điểm gốc là tâm và hình thay cho :
Như vậy, ta chỉ cần tính diện tích có hướng của phần giao giữa hình tròn và một tam giác có một đỉnh là tâm . Ta đặt gốc tọa độ tại . Đoạn gồm các điểm với , và các giao điểm của đoạn với đường tròn ứng với các nghiệm của phương trình bậc hai
Phương trình này có nhiều nhất hai nghiệm, nên các giao điểm chia đoạn thành nhiều nhất ba đoạn con. Mỗi đoạn con không chứa giao điểm nào ở bên trong, nên nằm hoàn toàn bên trong hoặc hoàn toàn bên ngoài hình tròn. Tương ứng, tam giác được chia thành các tam giác nhỏ (Hình 5), và ta tính phần giao của từng tam giác nhỏ với hình tròn:
Nếu đoạn nằm trong hình tròn, cả ba điểm , , đều thuộc hình tròn. Vì hình tròn là một tập lồi, toàn bộ tam giác nằm trong hình tròn, nên phần giao có diện tích có hướng bằng .
Nếu đoạn nằm ngoài hình tròn, mỗi tia xuất phát từ và đi qua một điểm của đoạn sẽ ra khỏi hình tròn trước khi chạm tới đoạn . Do đó, phần giao chính là hình quạt giới hạn bởi hai tia và , có diện tích có hướng bằng , trong đó là góc có hướng từ vector đến vector .
Để biết một đoạn con nằm trong hay nằm ngoài hình tròn, ta chỉ cần kiểm tra một điểm bất kỳ ở bên trong đoạn đó. Code kiểm tra hai điểm, tại vị trí và của đoạn, để hạn chế ảnh hưởng của sai số.
Cài đặt
Với mỗi truy vấn, thuật toán thực hiện như sau:
Nếu , miền là hình tròn lớn hơn trong hai hình tròn. Ta chọn là tâm của hình tròn này và cộng các đại lượng , trong đó mỗi đại lượng được tính bằng Bước 4.
Trong trường hợp còn lại, ta đổi sang hệ tọa độ ở Bước 2 và tính , , , và . Với mỗi , ta cắt tam giác thành ba phần như ở Bước 3, rồi cộng ba đại lượng tương ứng vào tổng.
Đáp án là trị tuyệt đối của tổng.
Mỗi tam giác chỉ cần một số lượng phép tính không đổi, nên mỗi truy vấn có độ phức tạp , và độ phức tạp của toàn bộ thuật toán là .
Độ chính xác: Mỗi số hạng có thể lớn tới cỡ diện tích của (khoảng ), và các số hạng dương, âm triệt tiêu lẫn nhau, trong khi đáp án có thể rất nhỏ nhưng vẫn cần sai số tuyệt đối không quá . Code xử lý vấn đề này bằng ba cách:
Dùng kiểu
long doublethay chodouble. Trên máy chấm Linux x86,long doublecó khoảng 19 chữ số có nghĩa, so với khoảng 16 chữ số củadouble.Cộng dồn đáp án bằng tổng Kahan (xem khung bên dưới), nhờ đó sai số gần như không tích lũy theo số lượng số hạng.
Giải phương trình bậc hai theo cách ổn định số. Xét phương trình . Khi lớn hơn nhiều so với , ta có , nên trong công thức nghiệm , một trong hai nghiệm là hiệu của hai số gần bằng nhau, và phép trừ này làm mất gần hết các chữ số có nghĩa. Để tránh điều đó, code chỉ tính trực tiếp nghiệm ứng với phép cộng hai số cùng dấu, rồi suy ra nghiệm còn lại từ định lý Viète :
Tổng Kahan
Vấn đề. Máy tính lưu mỗi số thực với một số lượng chữ số có nghĩa cố định (với double là 53 bit, tương đương khoảng 16 chữ số thập phân). Vì vậy, kết quả của mỗi phép cộng đều bị làm tròn. Khi cộng một số nhỏ vào một tổng lớn, các chữ số ở hàng thấp của số nhỏ không được lưu lại. Khi cộng liên tiếp số, các sai số làm tròn này có thể tích lũy, và trong trường hợp xấu nhất, sai số tăng tỉ lệ với .
Giả sử máy tính chỉ lưu được 4 chữ số có nghĩa (theo hệ thập phân), và ta cần tính . Kết quả chính xác là ; khi làm tròn tới 4 chữ số có nghĩa, kết quả tốt nhất có thể đạt được là . Ở phép cộng đầu tiên, , nhưng máy chỉ lưu được , nên phần bị mất (Hình 6). Nếu cộng theo cách thông thường, cả ba phép cộng đều mất , và kết quả cuối cùng là , lệch so với kết quả chính xác.
Ý tưởng. Sau mỗi phép cộng, thuật toán Kahan tính lại phần vừa bị làm tròn mất và bù phần đó vào phép cộng tiếp theo. Biến correction lưu phần bị mất của phép cộng gần nhất:
void add(ld x) { ld y = x - correction; ld next = sum + y; correction = (next - sum) - y; sum = next;}Ý nghĩa của từng dòng như sau:
y = x - correctionlà số hạng mới, đã được bù phần bị mất ở phép cộng trước.next = sum + ylà tổng mới; giá trị này đã bị làm tròn.next - sumlà phần thực sự đã được cộng vào tổng. Vìnextvàsumcó giá trị gần nhau, phép trừ này thường không bị làm tròn.correction = (next - sum) - ylà phần thực sự đã được cộng trừ đi phần cần cộng, tức là sai số của phép cộng vừa thực hiện. Đại lượng này âm nếu kết quả bị làm tròn xuống, và dương nếu kết quả bị làm tròn lên. Ở phép cộng tiếp theo, đại lượng này được trừ khỏi số hạng mới, nghĩa là phần bị mất được cộng bù trở lại.
Bảng sau chạy lại ví dụ trên với máy tính chỉ lưu 4 chữ số có nghĩa:
| Lần cộng | Cộng thông thường | y | next | correction | Tổng Kahan |
|---|---|---|---|---|---|
| 1 | |||||
| 2 | |||||
| 3 |
Tổng Kahan cho kết quả , đúng bằng kết quả chính xác sau khi làm tròn, trong khi cách cộng thông thường cho . Tổng quát, gọi là sai số tương đối của một phép làm tròn. Khi cộng số , sai số của cách cộng thông thường bị chặn bởi khoảng , còn sai số của tổng Kahan bị chặn bởi khoảng , gần như không phụ thuộc vào . Trong bài này, mỗi truy vấn cộng tới số hạng (ba số hạng cho mỗi tam giác), và các số hạng có giá trị lớn, triệt tiêu lẫn nhau, nên tổng Kahan giúp đáp án chính xác hơn đáng kể.
Lưu ý. Không biên dịch chương trình với cờ -ffast-math, cũng như không dùng #pragma GCC optimize("Ofast"). Với các tùy chọn này, trình biên dịch được phép biến đổi biểu thức theo các quy tắc đại số, chẳng hạn rút gọn (next - sum) - y thành vì next = sum + y, làm mất hoàn toàn phần bù.
Code
#include <bits/stdc++.h>using namespace std;
using ld = long double;
const ld DISC_EPS = 2e-15L;
struct Point { ld x = 0, y = 0; Point operator+(Point o) const { return {x + o.x, y + o.y}; } Point operator-(Point o) const { return {x - o.x, y - o.y}; } Point operator*(ld k) const { return {x * k, y * k}; }};
ld dot(Point a, Point b) { return a.x * b.x + a.y * b.y; }ld cross(Point a, Point b) { return a.x * b.y - a.y * b.x; }ld norm2(Point a) { return dot(a, a); }
struct Polygon { array<Point, 12> p{}; int n = 0;};
struct KahanSum { ld sum = 0; ld correction = 0;
void add(ld x) { ld y = x - correction; ld next = sum + y; correction = (next - sum) - y; sum = next; }};
Polygon clip_halfplane(const Polygon &a, Point normal, ld c) { Polygon b; if (!a.n) return b; auto value = [&](Point p) { return dot(normal, p) - c; };
for (int i = 0; i < a.n; ++i) { Point x = a.p[i]; Point y = a.p[(i + 1) % a.n]; ld dx = value(x); ld dy = value(y); ld eps_x = 1e-16L * (1 + fabsl(c) + fabsl(dot(normal, x))); ld eps_y = 1e-16L * (1 + fabsl(c) + fabsl(dot(normal, y))); bool inside_x = dx <= eps_x; bool inside_y = dy <= eps_y; if (inside_x) b.p[b.n++] = x; if (inside_x != inside_y) { ld t = dx / (dx - dy); t = clamp(t, 0.0L, 1.0L);
b.p[b.n++] = x + (y - x) * t; } }
return b;}
ld polygon_area(const Polygon &p) { ld result = 0; for (int i = 0; i < p.n; ++i) result += cross(p.p[i], p.p[(i + 1) % p.n]); return result / 2;}
int segment_circle_roots(Point a, Point b, ld r, ld roots[2]) { Point v = b - a; ld A = norm2(v); if (A == 0) return 0; ld B = 2 * dot(a, v); ld C = norm2(a) - r * r; ld four_ac = 4 * A * C; ld discriminant = fmal(B, B, -four_ac); ld scale = fabsl(B * B) + fabsl(four_ac) + 1; if (discriminant < -DISC_EPS * scale) return 0; discriminant = max((ld)0, discriminant); ld root = sqrtl(discriminant); if (root <= 1e-24L * sqrtl(scale)) { roots[0] = -B / (2 * A); return 1; } ld q = -0.5L * (B + copysignl(root, B)); if (fabsl(q) < 1e-30L) { roots[0] = (-B - root) / (2 * A); roots[1] = (-B + root) / (2 * A); } else { roots[0] = q / A; roots[1] = C / q; } if (roots[0] > roots[1]) swap(roots[0], roots[1]); return 2;}
ld edge_circle_area(Point a, Point b, ld r) { array<ld, 4> cuts{}; int count = 0; cuts[count++] = 0; cuts[count++] = 1; ld roots[2]; int root_count = segment_circle_roots(a, b, r, roots); for (int i = 0; i < root_count; ++i) { if (roots[i] > 1e-16L && roots[i] < 1 - 1e-16L) { cuts[count++] = roots[i]; } } sort(cuts.begin(), cuts.begin() + count); int unique_count = 0; for (int i = 0; i < count; ++i) { if (!unique_count || fabsl(cuts[i] - cuts[unique_count - 1]) > 1e-15L) { cuts[unique_count++] = cuts[i]; } } Point direction = b - a; ld answer = 0; ld r2 = r * r; for (int i = 0; i + 1 < unique_count; ++i) { ld left = cuts[i]; ld right = cuts[i + 1]; Point p = a + direction * left; Point q = a + direction * right; Point sample1 = a + direction * ((2 * left + right) / 3); Point sample2 = a + direction * ((left + 2 * right) / 3); ld tolerance = 1e-15L * (r2 + norm2(sample1) + norm2(sample2) + 1); bool inside = norm2(sample1) <= r2 + tolerance && norm2(sample2) <= r2 + tolerance; if (inside) { answer += cross(p, q) / 2; } else { answer += r2 * atan2l(cross(p, q), dot(p, q)) / 2; } }
return answer;}
ld polygon_circle_area(const Polygon &p, Point center, ld r) { ld answer = 0; for (int i = 0; i < p.n; ++i) { Point a = p.p[i] - center; Point b = p.p[(i + 1) % p.n] - center; answer += edge_circle_area(a, b, r); } return answer;}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int tests; cin >> tests; cout << fixed << setprecision(12); while (tests--) { int n; cin >> n; vector<Point> polygon(n); for (Point &p : polygon) { long long x, y; cin >> x >> y; p = {static_cast<ld>(x), static_cast<ld>(y)}; } int q; cin >> q; while (q--) { long long xs, ys, rs_input; long long xt, yt, rt_input; cin >> xs >> ys >> rs_input >> xt >> yt >> rt_input; Point start{static_cast<ld>(xs), static_cast<ld>(ys)}; Point target{static_cast<ld>(xt), static_cast<ld>(yt)}; ld rs = rs_input; ld rt = rt_input; Point delta = target - start; ld distance2 = norm2(delta); ld radius_delta = rt - rs; bool is_circle = distance2 <= radius_delta * radius_delta + 1e-24L; Point origin; Point axis_x{1, 0}; Point axis_y{0, 1}; Point s, t; ld radius = 0; ld distance = 0; ld height = 0; ld left_x = 0; ld right_x = 0; Point normal_top; Point normal_bottom; ld tangent_constant = 0; if (is_circle) { if (rs >= rt) { origin = start; radius = rs; } else { origin = target; radius = rt; } } else { distance = sqrtl(distance2); origin = (start + target) * 0.5L; axis_x = delta * (1 / distance); axis_y = {-axis_x.y, axis_x.x}; s = {-distance / 2, 0}; t = {distance / 2, 0}; height = sqrtl(max((ld)0, distance2 - radius_delta * radius_delta)); left_x = -distance / 2 - rs * radius_delta / distance; right_x = distance / 2 - rt * radius_delta / distance; normal_top = {-radius_delta / distance, height / distance}; normal_bottom = {-radius_delta / distance, -height / distance}; tangent_constant = (rs + rt) / 2; } vector<Point> transformed(n); for (int i = 0; i < n; ++i) { Point v = polygon[i] - origin; transformed[i] = {dot(v, axis_x), dot(v, axis_y)}; } KahanSum answer; for (int i = 0; i < n; ++i) { Polygon triangle; triangle.n = 3; triangle.p[0] = {0, 0}; triangle.p[1] = transformed[i]; triangle.p[2] = transformed[(i + 1) % n]; if (is_circle) { answer.add(polygon_circle_area(triangle, {0, 0}, radius)); continue; } Polygon left = clip_halfplane(triangle, {1, 0}, left_x); answer.add(polygon_circle_area(left, s, rs)); Polygon middle = clip_halfplane(triangle, {-1, 0}, -left_x); middle = clip_halfplane(middle, {1, 0}, right_x); middle = clip_halfplane(middle, normal_top, tangent_constant); middle = clip_halfplane(middle, normal_bottom, tangent_constant); answer.add(polygon_area(middle)); Polygon right = clip_halfplane(triangle, {-1, 0}, -right_x); answer.add(polygon_circle_area(right, t, rt)); } cout << fabsl(answer.sum) << '\n'; } }}Continue reading
Related reading
Shares tags, a category or a series with this post
Take another route
Something different from the latest posts