L. Dynamic Convex Hull 题解
[L. Dynamic Convex Hull ]( Problem - L - Codeforces ) 题解
预处理原凸包面积,枚举新点 对原凸包画切线,将两个切点和新点重建新凸包,求和新凸包中的面积贡献。
新凸包是有原有凸包与新点构成的凸包合并而来的,所以新凸包中的面积会有原凸包中的面积贡献,所以需要预处理优化。
具体看代码注释,博主懒~,所以注释交给deep seek加的,如有问题评论或私信~
#include <bits/stdc++.h>
using namespace std;
//-------------------------------------------------------------------------------------------
#define int long long
#define lost_R ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define P pair<int, int>
#define lowbit(x) (x & (-x))
#define dbg1(x) cout << "# " << x << endl
#define dbg2(x, y) cout << "# " << x << " " << y << endl
#define endl '\n'
const int mod = 998244353;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f3f3f3f3f;
const double Eps = 1e-9;
//--------------------------------------------------------------------------------------
int n, m;
struct Point {
int x, y;
int id; // 点的标识,用于区分原始点和查询点
Point(int x = 0, int y = 0,int id = 0) : x(x), y(y), id(id) {}
bool operator<(const Point &p) const {
return x < p.x || (x == p.x && y < p.y);
}
};
typedef Point Vector;
// 向量运算
Vector operator + (Vector A, Vector B) { return Vector(A.x + B.x, A.y + B.y); }
Vector operator - (Vector A, Vector B) { return Vector(A.x - B.x, A.y - B.y); }
Vector operator * (Vector A, double k) { return Vector(A.x * k, A.y * k); }
// 叉积
int Cross(Vector A, Vector B) { return A.x * B.y - A.y * B.x; }
// 符号函数
int Sign(int x) {
return x > 0 ? 1 : (x<0?-1:0);
}
vector<Point> a; // 存储凸包点集
vector<int> sum; // 前缀和数组,用于快速计算面积
// 判断三点是否左转(逆时针)
int turn_left(Point u, Point v, Point w) {
return Sign(Cross(v - u, w - u));
}
// 二分查找切点
int search(auto f) {
int l = 0, r = (int)a.size() - 1;
if (f(a[0], a.back())) {
while (l + 1 < r) {
int mid = (l + r) / 2;
if (f(a[mid], a[l]) && f(a[mid], a[mid - 1]))
l = mid;
else
r = mid;
}
return l;
} else {
while (l + 1 < r) {
int mid = (l + r) / 2;
if (f(a[mid], a[r]) && f(a[mid], a[mid + 1]))
r = mid;
else
l = mid;
}
return r;
}
}
// 获取点u的两条切线
vector<Point> get_tan(Point u) {
return {
a[search([&](auto x, auto y) { return turn_left(u, y, x) > 0; })],
a[search([&](auto x, auto y) { return turn_left(u, x, y) > 0; })]
};
}
// 使用Andrew算法构建凸包
vector<Point> build_convex_hull(vector<Point>& points) {
sort(points.begin(), points.end());
vector<Point> hull;
// 构建下凸包
for (int i = 0; i < points.size(); i++) {
while (hull.size() >= 2 &&
turn_left(hull[hull.size()-2], hull.back(), points[i]) <= 0) {
hull.pop_back();
}
hull.push_back(points[i]);
}
// 构建上凸包
int lower_size = hull.size();
for (int i = points.size()-2; i >= 0; i--) {
while (hull.size() > lower_size &&
turn_left(hull[hull.size()-2], hull.back(), points[i]) <= 0) {
hull.pop_back();
}
hull.push_back(points[i]);
}
// 移除重复的起点
if (hull.size() > 1) hull.pop_back();
return hull;
}
// 计算多边形面积的2倍
int area2(const vector<Point> &p) {
int res = 0;
for (int i = 0; i < p.size(); i++) {
int j = (i + 1) % p.size();
res += Cross(p[i], p[j]);
}
return abs(res);
}
// 判断点u是否在凸包内部
bool inside(Point &u) {
if (a.size() < 3) return false; // 不是严格凸包
// 二分查找所在的三角形扇区
int l = 1, r = a.size() - 2;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (turn_left(a[0], a[mid], u) >= 0)
l = mid;
else
r = mid - 1;
}
// 检查是否在三角形内
return turn_left(a[0], a[l], u) >= 0 &&
turn_left(a[l], a[l+1], u) >= 0 &&
turn_left(a[l+1], a[0], u) >= 0;
}
// 计算从点l到点r的累计面积(顺时针方向)
int Sum(int l, int r) {
if (l <= r)
return sum[r] - sum[l];
else
return sum[a.size()] - sum[l] + sum[r];
}
void solve() {
a.clear();
cin >> n;
for (int i = 0; i < n; i++) {
int x,y;
cin>>x>>y;
a.push_back({x,y});
// points[i].id = i; // 正数ID表示原始点
}
// 构建凸包
a = build_convex_hull(a);
n = a.size(); // 更新凸包点数
for(int i=0;i<n;i++){
a[i].id=i;
}
// 计算前缀和
sum.resize(n + 1);
sum[0] = 0;
for (int i = 0; i < n; i++) {
int j = (i + 1) % n;
sum[i + 1] = sum[i] + Cross(a[i], a[j]);
}
cin >> m;
while (m--) {
int k;
cin >> k;
vector<Point> b;
// 处理每个查询点
for (int i = 1; i <= k; i++) {
Point t;
cin >> t.x >> t.y;
t.id = -i; // 负数ID表示查询点
// 如果点在凸包内部则忽略
if (n >= 3 && inside(t)) continue;
// 获取切线并加入集合
auto tangent = get_tan(t);
b.push_back(t);
b.push_back(tangent[0]);
b.push_back(tangent[1]);
}
if (b.empty()) {
// 所有点都在凸包内部,直接输出凸包面积
cout << sum[n] << endl;
} else {
// 构建新的凸包
vector<Point> new_hull = build_convex_hull(b);
int ans = 0;
// 计算新凸包的面积贡献
for (int i = 0; i < new_hull.size(); i++) {
int j = (i + 1) % new_hull.size();
if (new_hull[i].id < 0 || new_hull[j].id < 0) {
// 连接的是查询点,直接计算叉积
ans += Cross(new_hull[i], new_hull[j]);
} else {
// 连接的是原始凸包点,使用前缀和快速计算
ans += Sum(new_hull[i].id, new_hull[j].id);
}
}
cout << abs(ans) << endl;
}
}
}
signed main() {
lost_R;
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}