把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【UOJ553】【UNR #4】己酸集合(分块)

点此看题面

  • 给定平面上\(n\)个点,\(q\)次询问到\((0,z_i)\)距离不超过\(r_i\)的点数。
  • \(n\le1.2\times10^4,q\le10^6\)

直线转化

直接列出判断式:

\[x^2+(y-z_i)^2\le r_i^2\\ x^2+y^2-2yz_i+z_i^2\le r_i^2\\ -2yz_i+(x^2+y^2)\le r_i^2-z_i^2 \]

也就是说,对于原图的每一个点我们都可以看成一条解析式为\(-2yz_i+(x^2+y^2)\)的直线,然后一次询问就是要求\((z_i,r_i^2-z_i^2)\)下方的直线数。

分块+暴枚交点

我们把序列分成若干块,假设每块大小为\(S\)

对于每个块,直接暴枚每对直线求出交点,显然交点个数是\(O(S^2)\)的,然后把它们按照横坐标排序。

接下来就是依次枚举每个交点,翻转在这个交点上相交的直线的上下顺序,这样就可以维护好横坐标取到每种值时所有直线的上下顺序。

因此我们把询问离线了,既然已经知道此时直线的上下顺序,直接\(lower\_bound\)一下就可以查询出下方直线数了。

复杂度为\(O(\frac nS(S^2+q)logS^2)\),当\(S=\sqrt q\)时取到最优复杂度\(O(n\sqrt qlog q)\)

代码:\(O(n\sqrt qlogq)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 12000
#define M 1000000
#define BS 1000
#define LL long long
#define DB long double
#define eps 1e-12
using namespace std;
int n,Qt,sz,bl[N+5],ans[M+5];struct Q {int p;LL z,b;I bool operator < (Con Q& o) Con {return z<o.z;}}q[M+5];
int nw;struct S {LL k,b;I bool operator < (Con S& o) Con {return k*nw+b<o.k*nw+o.b;}}s[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	#define D isdigit(oc=tc())
	int ff,OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0,ff=1;W(!D) ff=oc^'-'?1:-1;W(x=(x<<3)+(x<<1)+(oc&15),D);x*=ff;}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
struct P {int a,b;DB x,y;I bool operator < (Con P& o) Con {return fabs(x-o.x)>eps?x<o.x:y<o.y;}}p[BS*BS+5];
int rk[BS+5],vis[BS+5],St[BS+5];I void Solve(S *s,CI n)
{
	#define X -1987654321//乱取一个极小点,防止初始情况下直线相交
	RI i,j,ct=0;DB x;for(nw=X,sort(s+1,s+n+1),i=1;i<=n;++i) for(rk[i]=i,j=i+1;j<=n;++j)//枚举每对直线求交点
		s[i].k^s[j].k&&(x=1.0L*(s[j].b-s[i].b)/(s[i].k-s[j].k),p[++ct]=(P){i,j,x,s[i].k*x+s[i].b},0);//记录下直线编号
	RI g=1,T=0,Mn,Mx;for(sort(p+1,p+ct+1),i=1;i<=ct;i=j+1) if(p[j=i].x>X)//按横坐标枚举交点
	{
		W(g<=Qt&&q[g].z<p[i].x) nw=q[g].z,ans[q[g].p]+=upper_bound(s+1,s+n+1,(S){0,q[g].b})-s-1,++g;//处理此时询问,lower_bound查询
		#define Mark(x) (!vis[x]&&(Mn=min(Mn,rk[x]),Mx=max(Mx,rk[x]),vis[St[++T]=x]=1))//标记直线x经过交点
		Mn=n,Mx=1,Mark(p[i].a),Mark(p[i].b);//Mn,Mx分别维护最小最大排名
		W(j<=ct&&fabs(p[i].x-p[j+1].x)<eps&&fabs(p[i].y-p[j+1].y)<eps) ++j,Mark(p[j].a),Mark(p[j].b);//同一交点上的直线一起处理
		reverse(s+Mn,s+Mx+1);W(T) rk[St[T]]=Mn+Mx-rk[St[T]],vis[St[T--]]=0;//翻转顺序
	}
	W(g<=Qt) nw=q[g].z,ans[q[g].p]+=upper_bound(s+1,s+n+1,(S){0,q[g].b})-s-1,++g;//处理剩余询问
}
int main()
{
	RI i,x,y;for(read(n,Qt),sz=sqrt(Qt),i=1;i<=n;++i) read(x,y),s[i]=(S){-2*y,1LL*x*x+1LL*y*y},bl[i]=(i-1)/sz+1;//把点转化为直线
	for(i=1;i<=Qt;++i) read(x,y),q[i]=(Q){i,x,1LL*y*y-1LL*x*x};sort(q+1,q+Qt+1);//把询问转化为点,事先排好序
	for(i=1;i<=bl[n];++i) Solve(s+(i-1)*sz,min(i*sz,n)-(i-1)*sz);for(i=1;i<=Qt;++i) writeln(ans[i]);return clear(),0;//分别处理每个块
}
posted @ 2021-05-25 10:59  TheLostWeak  阅读(167)  评论(0编辑  收藏  举报