給定頂點按順時針順序的凸包 (n ≤ 100,000),以及嚴格在內部的兩點 A, B。求凸包上有多少對頂點 (p_i, p_j) 使得線段 p_i p_j 與線段 AB 嚴格相交(交點不在端點)。
向量 v1 = A - p_i, v2 = B - p_i。對候選點 p_mid,向量 v = p_mid - p_i。若 cross(v, v1) 與 cross(v, v2) 同號(或一個為 0),則 p_mid 在同側。
cross(v, v1) ≤ 0 且 cross(v, v2) ≤ 0 的位置 tp1(右側邊界)。cross(v, v1) ≥ 0 且 cross(v, v2) ≥ 0 的位置 tp2(左側邊界)。tp1 - tp2 - 1,累加答案。#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Pt { ll x, y; };
Pt operator-(Pt a, Pt b) { return {a.x-b.x, a.y-b.y}; }
ll cross(Pt a, Pt b) { return a.x*b.y - a.y*b.x; }
int sgn(ll x) { return (x>0)-(x<0); }
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n; Pt A, B;
while (cin >> n) {
vector p(2*n+1);
for (int i=1;i<=n;i++) cin >> p[i].x >> p[i].y;
cin >> A.x >> A.y >> B.x >> B.y;
for (int i=n+1;i<=2*n;i++) p[i] = p[i-n];
ll ans = 0;
for (int i=1;i<=n;i++) {
Pt v1 = A - p[i], v2 = B - p[i];
int l = i+1, r = i+n-1;
// 找右側邊界
while (l < r) {
int mid = (l+r)>>1;
Pt v = p[mid] - p[i];
if (sgn(cross(v, v1)) <= 0 && sgn(cross(v, v2)) <= 0) r = mid;
else l = mid+1;
}
int tp1 = l;
l = i+1; r = i+n-1;
// 找左側邊界
while (l < r) {
int mid = (l+r+1)>>1;
Pt v = p[mid] - p[i];
if (sgn(cross(v, v1)) >= 0 && sgn(cross(v, v2)) >= 0) l = mid;
else r = mid-1;
}
int tp2 = l;
if (tp1 != tp2) ans += tp1 - tp2 - 1;
}
cout << (ans/2) << '\n';
}
return 0;
}