题解 【P5525 [Ynoi2012] WC2016充满了失望】
command_block
2021-07-05 23:44:30
**题意** : 给出平面上的一个点集 $S$ 。
给出平面上的若干个圆,对于每个圆,判定其是否完全在 $S$ 的凸包的内部。
保证圆的半径变化不超过 $1$ 时答案不发生变化。
$n,m\leq 5\times 10^5$,时限 $\texttt{2s}$。
------------
将凸包拆分成上凸壳和下凸壳。我们只需分别判断圆是否在 上/下 凸壳内部。
下面只介绍上凸壳,将 $y$ 坐标翻转即可转化为下凸壳。
记 ${\rm convex}(S)$ 为点集 $S$ 的上凸壳(内部的区域)。
记 ${\rm contract}(T,r)$ 表示能完全放入平面区域 $T$ 中的半径为 $r$ 的圆的圆心的集合。
考虑随着 $r$ 的增大 ${\rm contract}\big({\rm convex}(S),r\big)$ 会如何变化。
若将 ${\rm convex}(S)$ 描述为半平面交,则变化相当于每个半平面缩小了 $r$ 单位距离。
在这个过程中,有些半平面可能不再在凸壳上,需要将其删除。
每个半平面只可能被两侧的半平面迫害掉,用**堆**维护每个半平面被迫害掉的时间。
如何计算 $l_1,l_3$ 迫害 $l_2$ 所需时间?
这是初中数学基础题:做两条角平分线,取交点到 $l_2$ 的距离。
![](https://cdn.luogu.com.cn/upload/image_hosting/v0wp6nzq.png)
当某个半平面被删除时,重新计算两侧的半平面的迫害时间。
对于一个正确的收缩凸壳,容易**二分**判定圆心是否在其中。
用**并查集**维护序列,能支持删除某个元素,快速查找某个元素的前后继。
复杂度 $O\big((n+m)\log n\big)$。
代码常数很大。
```cpp
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cmath>
#define db double
#define MaxN 500500
using namespace std;
namespace io {
char buf[5005000],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,5000000,stdin),p1==p2)?EOF:*p1++)
int rd() {
int x=0;char ch=getchar(),f=0;
while(ch<'0'||'9'<ch)f|=(ch=='-'),ch=getchar();
while('0'<=ch&&ch<='9')x=x*10+(ch^48),ch=getchar();
return f?-x:x;
}
}using io::rd;
const db eps=1e-3;
struct Point{db x,y;}p[MaxN];
bool cmpP(const Point &A,const Point &B)
{return fabs(A.x-B.x)<eps ? A.y<B.y : A.x<B.x;}
Point operator + (const Point &A,const Point &B){return (Point){A.x+B.x,A.y+B.y};}
Point operator - (const Point &A,const Point &B){return (Point){A.x-B.x,A.y-B.y};}
Point operator * (const Point &A,db x){return (Point){A.x*x,A.y*x};}
Point operator / (const Point &A,db x){return (Point){A.x/x,A.y/x};}
db operator ^ (const Point &A,const Point &B){return A.x*B.y-A.y*B.x;}
bool anticlo(Point A,Point B,Point C){return ((A-B)^(C-B))>eps;}
db gl(Point A){return sqrt(A.x*A.x+A.y*A.y);}
struct Line{Point a,b;};
db dis(Line l,Point p){return ((p-l.a)^(l.b-l.a))/gl(l.b-l.a);}
bool chk(Line l,Point p,db r){return dis(l,p)+eps>r;}
Point dir(Line l){return (l.b-l.a)/gl(l.b-l.a);}
Line shift(Line l,db dis)
{
Point d=dir(l)*dis;
swap(d.x,d.y);d.y*=-1;
return (Line){l.a+d,l.b+d};
}
Point inter(Line A,Line B)
{
db sl=(A.a-B.b)^(B.a-B.b),sr=(B.a-B.b)^(A.b-B.b);
return A.a+(A.b-A.a)*sl/(sl+sr);
}
db gr(Line A,Line B,Line C)
{
Point p1=inter(A,B),p2=inter(B,C);
Line l1=(Line){p1,p1-dir(A)+dir(B)}
,l2=(Line){p2,p2-dir(B)+dir(C)};
return fabs(dis(B,inter(l1,l2)));
}
struct Cir{Point o;int r,p;}s[MaxN];
bool cmpC(const Cir &A,const Cir &B){return A.r<B.r;}
int f[MaxN],c[MaxN];
int find(int u)
{return f[u]==u ? u : f[u]=find(f[u]);}
void merge(int u,int v){
u=find(u);v=find(v);
c[f[u]=v]+=c[u];
}
Line l[MaxN];db dt[MaxN];
#define Pr pair<db,int>
#define fir first
#define sec second
#define mp make_pair
priority_queue<Pr,vector<Pr>,greater<Pr>> q;
int nowr,top;
void upd(int p)
{
if (p==1||p==top)return;
db ndt=gr(l[find(p-1)],l[p],l[p+c[p]]);
if (dt[p]-ndt>eps)q.push(mp(dt[p]=ndt,p));
}
void del(int p){
merge(p,p-1);p=find(p);
upd(p);upd(p+c[p]);
}
bool qry(Point p)
{
int tl=2,tr=top-1;
while(tl<tr){
int mid=(tl+tr+1)>>1;
int t=find(mid);
if (t==1){tl=mid+1;continue;}
int pre=find(t-1);
if (inter(shift(l[pre],nowr),shift(l[t],nowr)).x<p.x+eps)tl=mid;
else tr=mid-1;
}return tl<=1||tl>=top||chk(l[find(tl)],p,nowr);
}
Point stk[MaxN];
int n,m,tn;
bool ans[MaxN];
void calc()
{
top=0;
for (int i=1;i<=n;i++){
while(top>1&&!anticlo(stk[top-1],stk[top],p[i]))top--;
stk[++top]=p[i];
}
top--;
for (int i=1;i<=top;i++){
l[i]=(Line){stk[i],stk[i+1]};
f[i]=i;c[i]=1;
}
while(!q.empty())q.pop();
for (int i=2;i<top;i++)
q.push(mp(dt[i]=gr(l[i-1],l[i],l[i+1]),i));
for (int i=1;i<=m;i++){
nowr=s[i].r;
while(!q.empty()&&q.top().fir+eps<nowr){
Pr now=q.top();q.pop();
if (now.fir-dt[now.sec]>eps)continue;
del(now.sec);
}
ans[s[i].p]&=chk(l[1],s[i].o,nowr);
ans[s[i].p]&=chk(l[top],s[i].o,nowr);
if (ans[s[i].p])ans[s[i].p]&=qry(s[i].o);
}
}
void solve()
{
n=rd();
for (int i=1;i<=n;i++){p[i].x=rd();p[i].y=rd();}
sort(p+1,p+n+1,cmpP);
m=rd();
for (int i=1;i<=m;i++){
s[i].o.x=rd();s[i].o.y=rd();s[i].r=rd();
ans[s[i].p=i]=1;
}sort(s+1,s+m+1,cmpC);
calc();
for (int i=1;i<=n;i++)p[i].y*=-1;
for (int i=1;i<=m;i++)s[i].o.y*=-1;
calc();
for (int i=1;i<=m;i++)putchar(ans[i] ? '1' : '0');puts("");
}
int main()
{
int T=rd();
while(T--)solve();
return 0;
}
```