思路:
(现在我还没有AC这道题,先记录一下思路吧T^T)
昨天膜拜了xhr犇的论文,get了一系列新姿势.
总的来说,保证修改相互独立的数据结构问题可以用如下三种方法来水:
1.对时间分治
题目要求:修改独立,允许离线
我们利用对时间分治,将问题转化为先给出若干修改,然后进行若干询问的形式.这样我们就可以先对修改进行预处理,随后回答每个询问,这样就避免了动态修改.
2.二进制分组
题目要求:修改独立,强制在线
我们按照顺序进行操作,同时维护已经完成的修改的二进制分组,当在后面加入一个修改的时候,我们暴力重构为新的分组;当回答询问时,我们在之前的\(O(\log n)\)的分组中分别累加答案.这样,我们就通过一个\(\log\)的代价避免了动态修改.
3.整体二分
这个与这道题无关就先不说了.
那么来说这道题.
首先有两种基本思路:
[1]利用圆的反演转化为动态半平面交问题
我们以圆心为反演中心,合适长度为半径进行反演,那么每个圆都被反演成一条不过原点的直线(在圆内区域形成一个半平面).我们对每个询问点也进行反演,不难发现若询问点在所有圆的并集之内,则反演点应该在所有直线的半平面交之内.
那么现在问题转化为:支持两个操作,加入一个半平面,询问一个点在不在之前插入的半平面的半平面交之内.
{1}貌似可以拿平衡树维护,\(O(n\log n)\),我表示跪了.
{2}按照时间分治,在一次分治内,我们处理出前一半操作中插入的半平面的半平面交,随后去判断后一半操作中的询问点是否在这个半平面交中,若不在,则表示这个询问点不在所有的圆中.于是这样我们可以做到\(O(n\log ^2n)\).
(至于如何快速判断一个点在不在一个凸包中,我们可以任取凸包内的一个点,将凸包上的点按照关于这个点的极角序排序,对于询问点,我们二分得到询问点在凸包上的哪两个点之间,随后利用\(O(1)\)判断询问点在不在三角形中.这样我们就做到了\(O(\log n)\)询问.)
{3}利用二进制分组,维护\(O(\log n)\)个分组的半平面交,容易发现重构半平面交的复杂度为\(O(n\log ^2n)\),对于每一个询问的复杂度也为\(\log ^2n\),因此总复杂度为\(O(n\log ^2n)\).
[2]通过等式变换转化为另一个问题
由于题目保证圆通过原点,我们考虑点\((x_0,y_0)\)在圆心为\((ox,oy)\)的圆内部或边界上的条件:
\[(x_0-ox)^2+(y_0-oy)^2\leq{ox^2+oy^2}\]
\[x_0^2+y_0^2\leq{2x_0{ox}+2y_0{oy}}\]
这样相当于是每一个询问是给定一个半平面,然后问之前出现过的所有圆形成的点是否都在半平面之内.并且要支持加点.
{1}利用平衡树做动态凸包,\(O(n\log n)\),同样是给跪了.
{2}按时间分治,对于每一次分治,我们预处理出前一半操作中的点形成的凸包,然后去判断后面一半操作中的询问半平面是否完全包含凸包.
我们没有必要作凸包,只需\(O(n)\)维护上下凸壳即可.对于后面的询问,我们可以直接二分,也可以预先将所有询问排序随后线性扫描.
这样就可以做到\(O(n\log ^2n)\)或是\(O(n\log n)\).
{3}二进制分组,我们将之前的\(n\)次加点操作分为\(O(\log n)\)组,然后对于每一次询问在每一个维护的结构中二分.这个方法支持强制在线,不过时间复杂度为\(O(n\log ^2n)\).
我目前写的做法是[1]{2},不过貌似精度死得很惨.反演半径居然会影响答案.
现在用[2]{2}的方法AC了.时间复杂度\(O(n\log n)\).
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
typedef double f2;
inline f2 sqr(const f2&x){return x*x;}
#define N 500010
struct Case{
int qte,lab;f2 x,y;
bool operator<(const Case&B)const{
if(qte!=B.qte)return qte<B.qte;
if(qte==0)return x<B.x||(x==B.x&&y<B.y);
if(qte==1)return(-x/y)<(-B.x/B.y);
}
}q[N],ql[N],qr[N],up[N],down[N];
int cntl,cntr,topup,topdown;
struct Point{
f2 x,y;
Point(){}
Point(f2 _x,f2 _y):x(_x),y(_y){}
Point operator-(const Point&B)const{return Point(B.x-x,B.y-y);}
};
f2 cross(const Point&A,const Point&B){return A.x*B.y-A.y*B.x;}
struct Line{
f2 A,B,C;
};
inline Line Getline(const Case&c){
Line l;
l.A=2*c.x,l.B=2*c.y,l.C=sqr(c.x)+sqr(c.y);
return l;
}
inline bool onleft(const Line&l,const Point&p){
return l.A*p.x+l.B*p.y>=l.C;
}
bool OK[N];
inline void Solve(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
register int i,j;
for(cntl=cntr=0,i=l;i<=r;++i)if(q[i].lab<=mid)ql[++cntl]=q[i];else qr[++cntr]=q[i];
int id=l;for(i=1;i<=cntl;++i)q[id++]=ql[i];for(i=1;i<=cntr;++i)q[id++]=qr[i];
int cnt=0;for(i=l;i<=mid;++i)cnt+=(q[i].qte==0);
if(cnt){
for(topup=topdown=0,i=l;i<=mid&&q[i].qte==0;++i){
while(topup>1&&(q[i].y-up[topup].y)*(up[topup].x-up[topup-1].x)>=(up[topup].y-up[topup-1].y)*(q[i].x-up[topup].x))--topup;
up[++topup]=q[i];
while(topdown>1&&(q[i].y-down[topdown].y)*(down[topdown].x-down[topdown-1].x)<=(down[topdown].y-down[topdown-1].y)*(q[i].x-down[topdown].x))--topdown;
down[++topdown]=q[i];
}
int ins;
for(ins=mid+1;ins<=r;++ins)if(q[ins].qte==1)break;
if(ins<=r){
i=ins;
for(j=1;j<=topdown;++j){
for(;i<=r&&(j==topdown||-q[i].x/q[i].y*(down[j+1].x-down[j].x)<=(down[j+1].y-down[j].y));++i)
if(!onleft(Getline(q[i]),Point(down[j].x,down[j].y)))OK[q[i].lab]=0;
}
i=r;
for(j=1;j<=topup;++j){
for(;i>=ins&&(j==topup||-q[i].x/q[i].y*(up[j+1].x-up[j].x)>=up[j+1].y-up[j].y);--i)
if(!onleft(Getline(q[i]),Point(up[j].x,up[j].y)))OK[q[i].lab]=0;
}
}
}
Solve(l,mid),Solve(mid+1,r);
}
int main(){
int n;scanf("%d",&n);
register int i;for(i=1;i<=n;++i)scanf("%d%lf%lf",&q[i].qte,&q[i].x,&q[i].y),q[i].lab=i;
for(i=1;i<=n;++i)if(q[i].qte==1)OK[i]=1;
for(i=1;i<=n&&q[i].qte==1;++i)OK[i]=0;
sort(q+1,q+n+1);
Solve(1,n);
for(i=1;i<=n;++i)if(q[i].qte==1)puts(OK[i]?"Yes":"No");
return 0;
}
(另外此题真的非常卡精度T^T)