最近点对:
对所有点按照 x坐标排序。然后分治,求出左半边和右半边的最近点对,对于两边的最近距离取较小的,记为 d。取从分治的中间点向左右各距离为 d d的范围中的所有点,把这些点按照 y坐标排序。对于每个点,扫一下与它 y坐标差小于等于 d的点,更新答案,可以证明(证明没看懂=_=)这样的点是很少的,不超过6个。
该题题意:给定n个点的坐标,第i个点消失后的最近点对距离为di,求di的和。
这题解法: 因为如果消失的点不是最近点对,那么对答案是没有影响的。
所以可以先求出没消失之前的最近点距离*(n-2),(n-2)指的是消失的点不是最近点对。再分别求两个最近点消失后的最近点距离。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#define x first
#define y second
using namespace std;
typedef long long LL;
struct point
{
int x,y;
}p[100005];
int tmp[100005];
int A,B;
LL mn=1ll<<54;
bool cmpxy(point a,point b)
{
if(a.x!=b.x) return a.x<b.x;
else return a.y<b.y;
}
bool cmpy(int a,int b)
{
return p[a].y<p[b].y;
}
LL dis(point a,point b)
{
return (LL)(a.x-b.x)*(a.x-b.x)+(LL)(a.y-b.y)*(a.y-b.y);
}
void update(int a,int b)//更新[1,n]的最近点对
{
if(mn>dis(p[a],p[b])){mn=dis(p[a],p[b]);A=a;B=b;}
}
LL closest(int L,int R)
{
if(L==R) return 1ll<<50;
if(L+1==R) {update(L,R);return dis(p[L],p[R]);}
int mid=(L+R)>>1;
LL ret=min(closest(L,mid),closest(mid+1,R));
int k=0;
for(int i=L;i<=R;i++)
{
if(1ll*(p[mid].x-p[i].x)*(p[mid].x-p[i].x)<=ret)
tmp[k++]=i;//存符合条件的点的下标
}
sort(tmp,tmp+k,cmpy);
for(int i=0;i<k;i++)
for(int j=i+1;j<k&&1ll*(p[tmp[j]].y-p[tmp[i]].y)*(p[tmp[j]].y-p[tmp[i]].y)<=ret;j++)
{
update(tmp[i],tmp[j]);
ret=min(dis(p[tmp[i]],p[tmp[j]]),ret);
}
return ret;
/*ret为[L,R]区间的最近点距离的d^2,所以前面都是类似这种
(p[mid].x-p[i].x)*(p[mid].x-p[i].x)<=ret
用来表示x坐标差小于d
}
int main()
{
#ifdef LOCAL
freopen("in.txt","r",stdin);
#endif // LOCAL
int T;
scanf("%d",&T);
while(T--)
{
int n;
LL ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].x,&p[i].y);
sort(p+1,p+1+n,cmpxy);
closest(1,n);
ans+=mn*(n-2);
mn=1ll<<50;
int a=A,b=B;
for(int i=1;i<=n;i++)
if(i==a) swap(p[i],p[n]);
closest(1,n-1);
ans+=mn;
mn=1ll<<50;
for(int i=1;i<=n;i++)
if(i==b) swap(p[i],p[n]);
closest(1,n-1);
ans+=mn;
mn=1ll<<50;
cout<<ans<<endl;
}
return 0;
}