Italian Cuisine

Italian Cuisine

极角排序,叉积计算面积

1. 题目核心转化

本题给定一个凸多边形(披萨)和一个圆(菠萝),要求经过两个顶点切一刀,求不包含菠萝的最大块面积的两倍。

由于多边形顶点按逆时针给出,一次合法的切割等价于枚举一条有向弦 (P_i \to P_j)。这条弦会将多边形分成两部分,我们考察弦右侧的链 (P_i, P_{i+1}, \dots, P_j) 加上弦 (P_j \to P_i) 围成的区域。

要使这块区域没有菠萝,等价于整个菠萝圆盘必须完全在有向弦 (P_i \to P_j) 的左侧(允许相切)。

2. 如何判断合法(不碰菠萝)

对于给定的弦 (P_i \to P_j),构造两个向量:

  • 弦的方向向量:(d = P_j - P_i = (dx, dy))
  • 指向圆心的向量:(v = O - P_i = (ox, oy)),其中 (O=(x_c, y_c)) 为圆心

计算二维叉积:
[
cr = d \times v = dx \cdot oy - dy \cdot ox
]

(1)方向判断:
顶点逆时针给出,考察的区域在弦右侧,因此圆心必须在弦左侧。
若 (cr > 0),说明圆心在弦的左侧;若 (cr \le 0),则菠萝在右侧或弦上,不合法。

(2)距离判断:
弦到圆心的垂直距离 (h = \frac{|cr|}{|d|})。
要求整个圆在左侧,即 (h \ge r)。
为了避免浮点数和开根号,直接两边平方:
[
\frac{cr2}{|d|2} \ge r^2 \implies cr^2 \ge r^2 \cdot |d|^2
]
其中 (|d|^2 = dx^2 + dy^2)。

综上,合法的条件为:
[
cr > 0 \quad \text{且} \quad cr^2 \ge r^2 \cdot (dx^2 + dy^2)
]

3. 面积计算(叉积前缀和)

题目要求面积的两倍,这刚好是多边形鞋带公式算出的结果。
利用叉积前缀和,可以 (O(1)) 算出链 (i \to \dots \to j \to i) 围成的面积的两倍:
[
S_2(i,j) = \sum_{k=i}^{j-1} (P_k \times P_{k+1}) + P_j \times P_i
]
预处理前缀和 (pre[k] = \sum_{t=0}^{k-1} P_t \times P_{t+1}),其中 (P \times Q = x_P y_Q - y_P x_Q)。
则:
[
S_2(i,j) = pre[j] - pre[i] + (P_j \times P_i)
]

4. 双指针优化

固定起点 (i),随着终点 (j) 的逆时针增加,区域越来越大,圆盘越来越容易碰到弦。因此合法终点 (j) 是一个连续的前缀。
随着起点 (i) 的增加,终点 (j) 也是单调不减的,可以使用双指针。

将顶点数组复制一倍处理环形,时间复杂度严格 (O(n))。
注意:坐标范围较大,叉积及平方比较需使用 __int128。

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;

__int128 cross(pll a, pll b) {
    return (__int128)a.first * b.second - (__int128)a.second * b.first;
}
void print128(__int128 x) {
    if(x == 0) {
        cout << 0 << "\n"; return;
    }
    string s;
    while(x > 0) {
        s += (char)('0' + x % 10);
        x /= 10;
    }
    reverse(s.begin(), s.end());
    cout << s << "\n";
}

void solve() {
    int n; cin >> n;
    ll x0, y0, r; cin >> x0 >> y0 >> r;

    vector<pll> vex(n * 2);
    for(int i = 0; i < n; i ++) {
        ll x, y; cin >> x >> y;
        vex[i] = vex[i + n] = {x, y};
    }

    vector<__int128> pres(n * 2 + 1, 0); //i 代表 i - 1 和 i叉积 pres[0] = 0
    for(int i = 0; i < n * 2; i ++) {
        pres[i + 1] = pres[i] + cross(vex[i], vex[i + 1]);
    }

    auto check = [&](int i, int j) -> bool {
        __int128 dx = vex[j].first - vex[i].first, dy = vex[j].second - vex[i].second;
        __int128 cx = x0 - vex[i].first, cy = y0 - vex[i].second;

        __int128 sp = dx * cy - dy * cx;
        if(sp <= 0) return false; //说明此时菠萝蜜在所求块里面
           
        __int128 d2 = dx * dx + dy * dy;
        __int128 r2 = (__int128)r * r;

        return sp * sp >= r2 * d2;
    };

    __int128 ans = 0;
    int j = 1;
    for(int i = 0; i < n; i ++) {
        if(j < i + 1) j = i + 1;
        
        while(j + 1 < i + n && check(i, j + 1)) j ++;
        if(check(i, j)) {
            __int128 area2 = pres[j] - pres[i] + cross(vex[j], vex[i]);
            if(area2 > ans) ans = area2;
        }
    }
    print128(ans);
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-11-03 21:23  thenrify  阅读(3)  评论(0)    收藏  举报