期望线性时间的最近点对
省流:这篇论文的复读 + 代码复现。
记号及约定:
筛选最近点对算法
令
筛选过程
令
将平面按照
记最小的使得
接下来是正确性证明,首先考虑两个事实:
- 满足
d(x)> 2\sqrt{2}b 的点,一定会被删除。 - 满足
d(x)< b 的点一定会被保留。
借用原论文中的一张图(其实是我重新画的)
夹在这个范围中间的点可能会被保留也可能会被删除。由于
令
因此
求解最近点对
此时我们已经得到了一个
对于每个点,检查其所在的块以及其 8-邻居 块,用这些块内的点更新答案(其余的块距离该点
代码实现细节
对于块长为
时间复杂度
先分析筛选过程的时间复杂度,检查邻居只需要检查
证明:给出一个粗略的估计,将
也就是说每轮迭代
即证。
对于第二部分求解
总时间复杂度
如何推广到三维
推广到三维是平凡的,我们只需要照猫画虎地把检查 8-邻居 改成检查 26-邻居 就好了。但是三维中用
实现了三维最近点对的代码,写得比较丑。
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define poly vector<p3>
#define int ll
#define i128 __int128
#define i3 ll,ll,ll
typedef long long ll;
typedef long double ld;
using namespace std;
const int N=1000010,lpw=1000003,B=19260817,V=1e11,INF=0x3f3f3f3f3f3f3f3f;
const ld eps=1e-9,inf=1e100;
const int dx[27]={-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1};
const int dy[27]={-1,-1,-1,0,0,0,1,1,1,-1,-1,-1,0,0,0,1,1,1,-1,-1,-1,0,0,0,1,1,1};
const int dz[27]={-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1};
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct p3{
ll x,y,z;//no double hiahia
p3(ll xx=0,ll yy=0,ll zz=0){x=xx,y=yy,z=zz;}
bool operator ==(const p3 &a)const{return x==a.x&&y==a.y&&z==a.z;}
bool operator !=(const p3 &a)const{return x!=a.x||y!=a.y||z!=a.z;}
p3 &operator +=(const p3 &b){x+=b.x,y+=b.y,z+=b.z;return *this;}
p3 &operator -=(const p3 &b){x-=b.x,y-=b.y,z-=b.z;return *this;}
friend istream &operator >>(istream &is,p3 &a){return is>>a.x>>a.y>>a.z;}
friend ostream &operator <<(ostream &os,p3 &a){return os<<'('<<a.x<<','<<a.y<<','<<a.z<<')';}
}O(0,0,0);
p3 operator +(const p3 &a,const p3 &b){return p3(a.x+b.x,a.y+b.y,a.z+b.z);}
p3 operator -(const p3 &a,const p3 &b){return p3(a.x-b.x,a.y-b.y,a.z-b.z);}
p3 operator *(const ld &x,const p3 &b){return p3((ld)x*b.x+eps,(ld)x*b.y+eps,(ld)x*b.z+eps);}
p3 operator /(const p3 &a,const ld &x){return p3((ld)a.x/x+eps,(ld)a.y/x+eps,(ld)a.z/x+eps);}
ld operator *(const p3 &a,const p3 &b){return (ld)a.x*b.x+(ld)a.y*b.y+(ld)a.z*b.z;}
ld dis(p3 a,p3 b){return sqrtl((a-b)*(a-b));}
int head[N],edge[N],Next[N],tot;
int T,n;
p3 ver[N];
poly p,q,s;
ld ans;
int hsh(p3 y){
y.x=(y.x%lpw+lpw)%lpw;
y.y=(y.y%lpw+lpw)%lpw;
y.z=(y.z%lpw+lpw)%lpw;
int x=(y.x*B%lpw*B%lpw+y.y*B%lpw+y.z)%lpw;
return (x+lpw)%lpw;
}
void clr(ld d){
tot=0;
for(p3 z:p)head[hsh(4.0*z/d)]=0;
}
void add(p3 y,ld d){
int x=hsh(4.0*y/d);
ver[++tot]=y,edge[tot]=1,Next[tot]=head[x],head[x]=tot;
}
void upd(p3 y,ld d){
int flg=0,x=hsh(4.0*y/d);
for(int i=head[x];i;i=Next[i]){
if(ver[i]!=y)continue;
edge[i]++;
flg=1;
break;
}
if(!flg)add(y,d);
}
ld solve(){
for(;;){
q.clear();
int j=rnd()%p.size();
ld d=inf;
for(int i=0;i<(int)p.size();i++)
if(i!=j)d=min(d,dis(p[i],p[j]));
if(fabs(d)<eps)return 0;
for(int i=0;i<(int)p.size();i++)upd(p[i],d);
for(int i=0;i<(int)p.size();i++){
for(int k=0,flg=1;k<27;k++){
p3 o=(4.0*p[i]/d)+p3(dx[k],dy[k],dz[k]);
int x=hsh(o),val=0;
for(int j=head[x];j;j=Next[j]){
if((4.0*ver[j]/d)!=o)continue;
val+=edge[j];
if(val>1)break;
}
flg&=(k==13)?val==1:val==0;
if(!flg){
q.pb(p[i]);
break;
}
}
}
clr(d);
if(q.size()<=1)return d;
p.swap(q);
}
return 0;
}
void _solve(){
cin>>n;
p.resize(n);
s.resize(n);
ans=inf;
for(int i=0;i<n;i++){
cin>>p[i];
p[i]+=p3(V,V,V);
s[i]=p[i];
}
ld d=solve();
assert(!tot);
if(fabs(d)<eps){
cout<<"0.00\n";
return;
}
for(int i=0;i<n;i++)
upd(s[i],4.0*d);
for(p3 z:s){
p3 o=(1.0*z/d);
int x=hsh(o);
for(int i=head[x];i;i=Next[i]){
if(ver[i]==z){
if(edge[i]>1){
p=s,clr(4.0*d);
cout<<"0.00\n";
return;
}
continue;
}
ans=min(ans,dis(z,ver[i]));
}
for(int k=0;k<27;k++){
if(k==13)continue;
int x=hsh(o+p3(dx[k],dy[k],dz[k]));
for(int i=head[x];i;i=Next[i])
ans=min(ans,dis(z,ver[i]));
}
}
p=s,clr(4.0*d);
cout<<fixed<<setprecision(2)<<ans<<'\n';
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>T;
while(T--)_solve();
return 0;
}