CF1906D题解
CF1906D
题意
给定一个 \(n\) 个顶点的凸多边形,多次进行询问。每次询问给出两个不在多边形内的点 \(P^1_j (A_j, B_j), P^2_j (C_j, D_j)\),问能否找到一个点 \(P^3\),使线段 \(P^1P^3, P^2P^3\) 不与凸多边形相交(可以相切),并求最短的 \(|P^1P^3| + |P^2P^3|\)。
解析
这题思路比较简单,但码量不小。
对于每次询问,先判断线段 \(P^1P^2\) 是否与凸多边形相交,若不相交直接输出 \(|P^1P^2|\) 即可;否则,可分别二分求 \(P^1, P^2\) 的切线(各两条,为以 \(P^1, P^2\) 为端点的射线),算出 \(P^1\) 各条切线与 \(P^2\) 切线的交点作为 \(P^3\) 计算出 \(\min\{|P^1P^3_i| + |P^2P^3_i|\}\) 作为答案,或者确认不存在这样一个交点 \(P^3\)。
对于二分求切线,如果点的横坐标不大于或不小于凸包上的任意点的横坐标,则把凸包分成上凸包和下凸包分别进行二分即可;否则,可按点的横坐标将凸包分割成左凸包和右凸包分别进行二分。
关于线段是否与凸多边形相交,将 \(P^1, P^2\) 视作点光源,判断 \(P^1\) 与 \(P^2\) 光线的可见点集是否有交,若交集内有多于 1 个元素则线段不与凸包相交;或者如果交集内正好有 1 个元素点 \(Z\),则可求 \(\overrightarrow{P^1P^2} \times \overrightarrow{P^1Z}\) 或 \(\overrightarrow{P^2P^1} \times \overrightarrow{P^2Z}\) 进行判断。
代码
#include <bits/stdc++.h>
#include <unordered_map>
#define LL long long
#define pii pair<int, int>
#define pll pair<LL, LL>
#define double long double
#define pdd pair<double, double>
#define eps 1e-9
using namespace std;
//直线
struct line
{
pdd p, v;
line(pll p, pll v)
{
this->p = (pdd)p;
this->v = (pdd)v;
}
};
pll operator + (pll l, pll r)
{
return make_pair(l.first + r.first, l.second + r.second);
}
pll operator - (pll l, pll r)
{
return make_pair(l.first - r.first, l.second - r.second);
}
//点乘,下同
LL dot(pll l, pll r)
{
return l.first * r.first + l.second * r.second;
}
//叉乘,下同
LL cross(pll l, pll r)
{
return l.first * r.second - l.second * r.first;
}
pdd operator + (pdd l, pdd r)
{
return make_pair(l.first + r.first, l.second + r.second);
}
pdd operator - (pdd l, pdd r)
{
return make_pair(l.first - r.first, l.second - r.second);
}
double dot(pdd l, pdd r)
{
return l.first * r.first + l.second * r.second;
}
//求两点距离
double dis(pdd l, pdd r)
{
return sqrt(dot(l - r, l - r));
}
double cross(pdd l, pdd r)
{
return l.first * r.second - l.second * r.first;
}
//求直线交点
pdd getNode(line l, line r)
{
double s1 = cross(l.v, r.p - l.p), s2 = cross(l.v, r.p + r.v - l.p);
return make_pair((r.p.first * s2 - (r.p.first + r.v.first) * s1) / (s2 - s1), (r.p.second * s2 - (r.p.second + r.v.second) * s1) / (s2 - s1));
}
int n;
pll ps[200005], ps1[100005];
map<pll, int> mp;
//二分求切点
pair<pll, pll> getPT(pll p)
{
pair<pll, pll> res;
if (p.first <= ps1[1].first)
{
int l = mp[ps1[1]], r = mp[ps1[n]];
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], ps[mid + 1] - p) < 0)
r = mid;
else
l = mid + 1;
}
res.first = ps[l];
l = mp[ps1[n]], r = mp[ps1[1]];
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], p - ps[mid]) > 0)
l = mid + 1;
else
r = mid;
}
res.second = ps[l];
}
else if (p.first >= ps1[n].first)
{
int l = mp[ps1[n]], r = mp[ps1[1]];
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], ps[mid + 1] - p) < 0)
r = mid;
else
l = mid + 1;
}
res.first = ps[l];
l = mp[ps1[1]], r = mp[ps1[n]];
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], p - ps[mid]) > 0)
l = mid + 1;
else
r = mid;
}
res.second = ps[l];
}
else
{
if (cross(ps1[1] - p, p - ps1[n]) < 0 || !cross(ps1[1] - p, p - ps1[n]) && !cross(ps[mp[ps1[n]] + 1] - ps1[n], ps1[1] - ps1[n]))
{
int L = mp[ps1[n]], R = mp[ps1[1]];
if (R < L)
R += n;
int div = lower_bound(ps + L, ps + R + 1, p, greater<pll>()) - (ps + L);
int l = L + div, r = R;
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], ps[mid + 1] - p) < 0)
r = mid;
else
l = mid + 1;
}
res.first = ps[l];
l = L, r = L + div - 1;
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], p - ps[mid]) > 0)
l = mid + 1;
else
r = mid;
}
res.second = ps[l];
}
else
{
int L = mp[ps1[1]], R = mp[ps1[n]];
if (R < L)
R += n;
int div = lower_bound(ps + L, ps + R + 1, p) - ps - L;
int l = L + div, r = R;
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], ps[mid + 1] - p) < 0)
r = mid;
else
l = mid + 1;
}
res.first = ps[l];
l = L, r = L + div - 1;
if (r < l)
r += n;
while (l < r)
{
int mid = l + r >> 1;
if (cross(ps[mid + 1] - ps[mid], p - ps[mid]) > 0)
l = mid + 1;
else
r = mid;
}
res.second = ps[l];
}
}
return res;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int q;
pll p0, p1;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> ps[i].first >> ps[i].second;
ps1[i] = ps[n + i] = ps[i];
mp[ps[i]] = i;
}
sort(ps1 + 1, ps1 + n + 1);
cin >> q;
while (q--)
{
cin >> p0.first >> p0.second >> p1.first >> p1.second;
pair<pll, pll> res1 = getPT(p0), res2 = getPT(p1);
double ans = 1e25; //初始值一定要开大
int r1 = mp[res1.first], l1 = mp[res1.second], r2 = mp[res2.first], l2 = mp[res2.second];
if (r1 < l1)
r1 += n;
if (r2 < l2)
r2 += n;
//判断线段是否与凸包相交
if (!(l1 >= r2 || l2 >= r1))
ans = dis(p0, p1);
else if (l1 == r2 && cross(p0 - p1, ps[l1] - p1) >= 0)
ans = dis(p0, p1);
else if (l2 == r1 && cross(p1 - p0, ps[l2] - p0) >= 0)
ans = dis(p0, p1);
if (r1 > n && r2 <= n)
l2 += n, r2 += n;
else if (r2 > n && r1 <= n)
l1 += n, r1 += n;
if (!(l1 >= r2 || l2 >= r1))
ans = dis(p0, p1);
else if (l1 == r2 && cross(p0 - p1, ps[l1] - p1) >= 0)
ans = dis(p0, p1);
else if (l2 == r1 && cross(p1 - p0, ps[l2] - p0) >= 0)
ans = dis(p0, p1);
//求切线交点并计算答案
if (cross(res1.first - p0, res2.first - p1))
{
pdd nd = getNode(line(p0, res1.first - p0), line(p1, res2.first - p1));
if (dot((pdd)(res1.first - p0), nd - (pdd)p0) > eps && dot((pdd)(res2.first - p1), nd - (pdd)p1) > eps)
ans = min(ans, dis(nd, p0) + dis(nd, p1));
}
if (cross(res1.first - p0, res2.second - p1))
{
pdd nd = getNode(line(p0, res1.first - p0), line(p1, res2.second - p1));
if (dot((pdd)(res1.first - p0), nd - (pdd)p0) > eps && dot((pdd)(res2.second - p1), nd - (pdd)p1) > eps)
ans = min(ans, dis(nd, p0) + dis(nd, p1));
}
if (cross(res1.second - p0, res2.first - p1))
{
pdd nd = getNode(line(p0, res1.second - p0), line(p1, res2.first - p1));
if (dot((pdd)(res1.second - p0), nd - (pdd)p0) > eps && dot((pdd)(res2.first - p1), nd - (pdd)p1) > eps)
ans = min(ans, dis(nd, p0) + dis(nd, p1));
}
if (cross(res1.second - p0, res2.second - p1))
{
pdd nd = getNode(line(p0, res1.second - p0), line(p1, res2.second - p1));
if (dot((pdd)(res1.second - p0), nd - (pdd)p0) > eps && dot((pdd)(res2.second - p1), nd - (pdd)p1) > eps)
ans = min(ans, dis(nd, p0) + dis(nd, p1));
}
if (ans >= 1e25)
printf("-1\n");
else
printf("%.12LF\n", ans);
}
}
最后祝各位顺利AC。>w<

浙公网安备 33010602011771号