BZOJ 4140: 共点圆加强版(圆的反演+二进制分组+半平面交)

该博客介绍了如何解决BZOJ 4140问题,重点讲解了利用圆的反演技术简化几何问题,将圆转化为半平面交,并应用二进制分组处理强制在线动态半平面交。博主提到这种方法在效率上可与CDQ分治媲美,并给出了实现AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

首先圆不好处理,可以用一些骚操作:反演。
几何上的(圆)反演是指确定一个反演中心 O O O和反演半径 r r r,那么对于一条以反演中心为端点的射线上的两点 P 1 , P 2 P_1,P_2 P1,P2并且 O P 1 × O P 2 = r 2 OP_1 \times OP_2 = r^2 OP1×OP2=r2 P 1 P_1 P1 P 2 P_2 P2的反演后的点, P 2 P_2 P2 P 1 P_1 P1的反演后的点。

那么过反演中心的圆将会被反演成一条直线,直线的两侧,有一侧是圆内的点反演而成,另一侧是圆外的点反演而成,那么判断在圆内可以转换为判定在半平面内。
那么可以做半平面交。

但是这是强制在线动态半平面交,就需要使用二进制分组

这个东西很强,实测不比CDQ分治慢(BZOJ 2961 共点圆),还可以来搞强制在线动态AC自动机。
最后半平面交直接被板就行。
注意一开始没有圆的所有询问输出 N O NO NO.!!!
貌似没有卡我的精度。

AC Code:

#include<bits/stdc++.h>
#define maxn 500005
#define eps 1e-7
#define inf 1e11
using namespace std;

int n;
int sgn(double a){ return a<-eps?-1:a>eps?1:0; }
struct Point{
	double x,y;
	Point(double x=0,double y=0):x(x),y(y){}
	Point operator +(const Point &B)const{ return Point(x+B.x,y+B.y); }
	Point operator -(const Point &B)const{ return Point(x-B.x,y-B.y); }
	double operator *(const Point &B)const{ return x*B.y-y*B.x; }
	Point operator *(const double &B)const{ return Point(x*B,y*B); }
	double Len()const{ return sqrt(x*x+y*y); }
}qp[maxn];
int qL[maxn],l,r;

bool inLeft(const Point &P,const Point &A,const Point &B){
	return (B-A)*(P-A)>0;
}
bool onLeft(const Point &P,const Point &A,const Point &B){
	return sgn((B-A)*(P-A))>=0;
}
/*
bool cmp(const int &u,const int &v){ 
	if(sgn(L[u].ag-L[v].ag)) return L[u].ag < L[v].ag;
	return inLeft(L[u].A,L[v].A,L[v].B);
}*/
struct Line{
	Point A,B;
	double ag;
	Line(Point A=0,Point B=0):A(A),B(B){ag=atan2(B.y-A.y,B.x-A.x);}
	bool operator <(const Line &B)const{
		if(sgn(ag-B.ag)) return ag<B.ag;
		return inLeft(A,B.A,B.B);
	}
}L[maxn];
int tot;
Point Ipt(const Point &p1,const Point &p2,const Point &p3,const Point &p4){
	Point u = p2-p1, v = p4-p3,w=p2-p4;
	return p2+(p1-p2)*((w*v)/(u*v));
}

int cnt_g,siz[30];
vector<int>G[30];
vector<double>GP[30];

int main(){
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	scanf("%d",&n);
	int sum=0;
	double x,y,R=1000;
	for(int i=1,op;i<=n;i++){
		scanf("%d%lf%lf",&op,&x,&y);x+=sum,y+=sum;
		if(op == 0){
			Point P = Point(2*x,2*y);
			double tmp = P.Len();
			P = P * (R * R / (tmp * tmp));
			L[++tot]=Line(P,P+Point(y,-x));
			siz[++cnt_g] = 0;
			G[cnt_g].clear();
			G[cnt_g].push_back(tot);
			for(;cnt_g>=2 && siz[cnt_g]==siz[cnt_g-1];cnt_g--){
				siz[cnt_g-1]++;
				int sz1 = G[cnt_g-1].size(), sz2 = G[cnt_g].size();
				int u = cnt_g-1 , v = cnt_g;
				static int q[maxn];
				for(int i=0,j=0,k=0;i<sz1||j<sz2;)
					if(i<sz1 && (j>=sz2 || L[G[u][i]]<L[G[v][j]])) q[k++] = G[u][i++];
					else q[k++] = G[v][j++];
						
				l=r=0;
				qL[r++] = q[0];
				for(int i=1,u;i<sz1+sz2;i++)
					if(sgn(L[u=q[i]].ag-L[q[i-1]].ag)){
						for(;r-l>=2 && !inLeft(qp[r-2],L[u].A,L[u].B);r--);
						//for(;r-l>=2 && !inLeft(qp[l],L[u].A,L[u].B);l++);
						qL[r++] = u;
						if(r-l>=2)
							qp[r-2] = Ipt(L[qL[r-2]].A,L[qL[r-2]].B,L[u].A,L[u].B);
					}
				//for(;r-l>=2 && !inLeft(qp[l],L[qL[r-1]].A,L[qL[r-1]].B);l++);
				for(;r-l>=3 && !inLeft(qp[r-2],L[qL[l]].A,L[qL[l]].B);r--);
				G[u].resize(r-l);
				//printf("@%d %d\n",l,r);
				GP[u].resize(r-l-1);
				for(int i=l;i<r;i++){
					G[u][i-l] = qL[i];
					if(i<r-1){
						GP[u][i-l]=atan2(qp[i].y-inf,qp[i].x);
					}
				}
			}
		}
		else{
			bool flg = (cnt_g==0);
			Point P = Point(x,y);
			double tmp = P.Len();
			P = P * (R * R / (tmp * tmp));
			x = P.x , y = P.y;
			double xl = atan2(y-inf,x);
			for(int i=1;i<=cnt_g;i++){
				int u = G[i][lower_bound(GP[i].begin(),GP[i].end(),xl) - GP[i].begin()];
				if(!onLeft(P,L[u].A,L[u].B)){ flg = 1 ; break; }
			}
			if(!flg) puts("Yes"),sum++;
			else  puts("No");
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值