d135: 00143 - Orchard Trees

Tags: computational-geometry, precision


出處:https://zerojudge.tw/ShowProblem?problemid=d135
提交:https://zerojudge.tw/Submissions?problemid=d135&account=allllllan123456


問題:考慮一塊正方形 $[0,100]\times[0,100]$,其中全部的格子點共 $99^2$ 個都不在邊界上,現在給你三個不彼此重疊的頂點座標 (不限定在格子點上) 皆精準到小數點以下兩位,請問由這些點所形成的三角形區域 (含邊界) 總共覆蓋了幾個格子點?


解法:這題的作法非常單純,因為三角形為凸多邊形,任何一刀割線都不會超出三角形的範圍,所以我們可以在每個 x 坐標上都畫垂直的一刀,如果割線有碰觸到三角形,都必定至少會跟兩條三角形的邊有交點,取這些交點之中的最大 y 座標的無條件捨去,及最小 y 座標的無條件進位,就能分別得到此 x 坐標在該三角形內的最上格子點與最下格子點,兩個高度相減再加一就能得到三角形於該 x 座標上所覆蓋的格子點數量,最後再從 x=1 加總到 x=99 就是答案。真正困難的地方在於實作時精度的處理,讀取座標時不能直接用 double,而是要如程式碼一樣手動讀取後再放大為 100 倍,等於說之後的每個格子點邊長其實也已經跟著變 100 倍,另外比較最上面與最下面格子點的時候也必須以有理數的形式比較,最後如何計算縮小回 1/100 倍的無條件捨去/進位也需要點技巧,頗麻煩的,想要 AC 還是得花點心思。


附註:
[1] http://onlinejudgesolution.blogspot.com/2017/04/uva-solution-143-orchard-trees-solution.html。網路上的作法幾乎都是直接檢查每個格子點有沒有落在三角形上/內,這樣子的作法雖然也是簡單,但是它的效率是跟正方形面積成正比,和我們的作法是跟三角形邊長成正比相較起來實在是差了許多。


 1#include <bits/stdc++.h>
 2using namespace std;
 3
 4// Ref: http://onlinejudgesolution.blogspot.com/2017/04/uva-solution-143-orchard-trees-solution.html
 5
 6typedef pair<int, int> frac, pii, point;
 7
 8int scaleDouble(char *str) { /* 100 times */
 9    int sum = 0, count = INT_MAX;
10    for (int i=0; str[i]; i++)
11        if ('0'<=str[i] && str[i]<='9') {
12            sum = sum*10 + str[i]-'0';
13            if (count <= 2) count--;
14        }
15        else count = 2;
16    if (count == INT_MAX) count = 2;
17    while (count--) sum *= 10;
18    return sum;
19}
20
21// assume p1's x-coordinate <= p2's x-coordinate.
22frac find_height(const point &p1, const point &p2, int x) {
23    if (x < p1.first || p2.first < x) return frac(0,0);
24    return frac(p1.second*(p2.first-x) + p2.second*(x-p1.first), p2.first-p1.first);
25    // 1st int ~ 10^8; 2nd int ~ 10^4.
26}
27
28int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); // IO 優化
29    char str[2][7]; int x, y; point arr[3];
30    while (true) {
31        for (int i=0; i<3; i++)
32            cin >> str[0] >> str[1], x = scaleDouble(str[0]), y = scaleDouble(str[1]), arr[i] = point(x, y);
33        if (!arr[0].first && !arr[0].second && !arr[1].first && !arr[1].second && !arr[2].first && !arr[2].second) break;
34        assert(arr[0]!=arr[1] && arr[0]!=arr[2] && arr[1]!=arr[2]);
35        int sum = 0; sort(arr, arr+3); // ensure ascending order of x-coordinates.
36        for (int i=1; i<=99; i++) {
37            frac maX(0,1), miN(10001,1);
38            for (const auto &pii: {pii(0,1), pii(0,2), pii(1,2)}) {
39                frac temp = find_height(arr[pii.first], arr[pii.second], i*100);
40                if (temp.second != 0) { // only if two x-coordinates are different.
41                    if ((long long)temp.first * maX.second > (long long)maX.first * temp.second) maX = temp;
42                    if ((long long)temp.first * miN.second < (long long)miN.first * temp.second) miN = temp;
43                }
44            }
45            if (miN.first != 10001 * miN.second) // if intersecting (do not use maX!=frac(0,1) since it is a legal value)
46                sum += min(99, maX.first/maX.second/100) - max(1, miN.first/miN.second/100
47                    + (miN.first/miN.second%100>0 || miN.first%miN.second>0)) + 1;
48        }
49        cout << setw(4) << sum << '\n';
50    }
51    return 0;
52}

no image