题目大意:
已知一个凸包的前提下,每次查询给出一个三角形,求平面内可以和三角形内的点构成射线穿过凸包但构成线段不穿过凸包的点的面积。
解题思路:
(因为官方思路更加清晰,所以这里借用官方的思路)
AC代码:
#include <bits/stdc++.h>
#define ft first
#define sd second
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define seteps(N) fixed << setprecision(N)
#define endl "\n"
const int maxn = 1e5 + 10;
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const ll mod = 1e9 + 7;
struct Point {
ll x, y;
Point operator - (Point p) {return {x - p.x, y - p.y};}
bool operator == (Point p) {return ((x == p.x) && (y == p.y));}
ll operator ^ (Point p) {return x * p.y - y * p.x;}
ll dis(Point p) {return hypot(x - p.x, y - p.y);}
} tri[4];
Point id;
bool cmp(Point a, Point b) {
double x = (a - id) ^ (b - id);
if (x > 0) return true;
if (x == 0 && a.dis(id) < b.dis(id)) return true;
return false;
}
ll multi(Point p1, Point p2, Point p3) {return (p2 - p1) ^ (p3 - p2);}
struct Pol {
int n;
Point p[maxn]; //凸包是逆时针旋转
ll s[maxn];
void init() {
for (int i = 3; i <= n; i++)
s[i] = abs((p[i - 1] - p[1]) ^ (p[i] - p[1])) + s[i - 1]; //算面积
}
void convex() { //构建凸包
int k = 1;
for (int i = 2; i <= n; i++)
if (p[i].x < p[k].x || p[i].x == p[k].x && p[i].y < p[k].y) //找最左边的,以及最下面的都行
k = i;
swap(p[1], p[k]);
id = p[1];
sort (p + 2, p + n + 1, cmp);
int t = 1;
for (int i = 2; i <= n; i++) {
while (t > 1 && multi(p[t - 1], p[t], p[i]) <= 0) t--;
p[++t] = p[i];
}
n = t;
}
template <typename F>
int find(F f) { //凸包从1开始
bool up = f(1, 2);
int l = 1, r = n + 1;
while (l != r) {
int m = (l + r) / 2;
if(f(1, m)){
if (up) l = m + 1;
else r = m;
} else {
if (f(m, m + 1)) r = m;
else l = m + 1;
}
}
return l;
}
pii findtangent(Point q) { //求凸包外一点的两条切线(逆十字板子)
int l=find([&](int i,int j){return ((p[i] - q) ^ (p[j] - q)) < 0;}),
r=find([&](int i,int j){return ((p[i] - q) ^ (p[j] - q)) > 0;});
return {l, r};
}
} pol, pol_tri;
pii seg[4];
int vis[20];
ll solve() {
memset(vis, 0, sizeof(vis));
ll ans = 0;
pol_tri.convex();
pol_tri.p[pol_tri.n + 1] = pol_tri.p[1];
pol_tri.init();
ans = pol_tri.s[pol_tri.n]; //初始答案,即图中的粉色区域
int op;
pol_tri.p[0] = pol_tri.p[pol_tri.n];
for (int i = 1; i <= pol_tri.n; i++) { //找到公切线上经过旧凸包上的点
for (int j = 1; j <= 3; j++) {
if (pol_tri.p[i] == pol.p[seg[j].ft])
vis[i] = seg[j].ft;
else if (pol_tri.p[i] == pol.p[seg[j].sd])
vis[i] = seg[j].sd;
}
}
for (int i = 1; i <= pol_tri.n; i++) if (vis[i] > pol.n) vis[i] -= pol.n; //因为可能有n+1,所以减去n
vis[0] = vis[pol_tri.n];
for (int i = 1; i <= pol_tri.n; i++) {
if (vis[i] && !vis[i - 1]) { //新凸包也是逆时针的,找到第一个在旧凸包上的点
op = i;
break;
}
}
if (!vis[op % pol_tri.n + 1]) return ans - pol.s[pol.n]; //如果只有一个切点,则说明旧凸包包含在新凸包内
ll s = 0;
//计算图中的紫色区域 或 旧凸包减去紫色区域的面积
//ans-s代表s恰好是紫色区域
//ans - (pol.s[pol.n] - s) (pol.s[pol.n] - s)才为紫色区域
s = abs((pol_tri.p[op] - pol.p[1]) ^ (pol_tri.p[op % pol_tri.n + 1] - pol.p[1]));
s += pol.s[min(vis[op], vis[op % pol_tri.n + 1])] + (pol.s[pol.n] - pol.s[max(vis[op], vis[op % pol_tri.n + 1])]);
//通过判断旧凸包开始的点来得出s是哪块区域
ll sgn1 = (pol.p[vis[op % pol_tri.n + 1]] - pol.p[vis[op]]) ^ (pol_tri.p[op - 1] - pol_tri.p[op]);
ll sgn2 = (pol.p[vis[op % pol_tri.n + 1]] - pol.p[vis[op]]) ^ (pol.p[1] - pol_tri.p[op]);
//分类讨论
if (vis[op] == 1) return ans - s; //切点恰好就是旧凸包开始的那个点
else if (vis[op % pol_tri.n + 1] == 1) return ans - (pol.s[pol.n] - s);
else {
if (sgn1 > 0 && sgn2 > 0) return ans - s;
else return ans - (pol.s[pol.n] - s);
}
}
int main() {
IOS; //开IOS,不然cin,cout会tle
int _, q;
cin >> _;
while (_--) {
cin >> pol.n;
for (int i = 1; i <= pol.n; i++)
cin >> pol.p[i].x >> pol.p[i].y; //逆时针输入
pol.p[pol.n + 1] = pol.p[1];
pol.init();
cin >> q;
while (q--) {
for (int i = 1; i <= 3; i++)
cin >> tri[i].x >> tri[i].y;
for (int i = 1; i <= 3; i++)
seg[i] = pol.findtangent(tri[i]);
pol_tri.n = 0;
for (int i = 1; i <= 3; i++) { //将六个切点和三角形上的点构建一个新凸包,新凸包上的和旧凸包上相同的点就是三角形和旧凸包公切线经过的点
pol_tri.p[++pol_tri.n] = tri[i];
pol_tri.p[++pol_tri.n] = pol.p[seg[i].ft];
pol_tri.p[++pol_tri.n] = pol.p[seg[i].sd];
}
cout << solve() << endl;
}
}
}