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;
}

浙公网安备 33010602011771号