【概述】
当给出一些二维平面的点时,记两点间距离为曼哈顿距离,此时的最小生成树,称为曼哈顿最小距离生成树。
对于 n 个点来说,最朴素的做法是暴力求出所有所有点两两之间的曼哈顿距离,然后再跑最小生成树算法,以得到曼哈顿最小距离生成树,但这样来做,由于总边数有 n^2 条,时间复杂度会达到 O(n^3) 或 O(n^2 logn)
对于 Kruskal 来说,针对这种曼哈顿距离的 MST 问题,实际上真正用到的点远没有 n^2 之多。
【原理】
考虑构建曼哈顿最小距离生成树时,每个点会与什么样的点连边
以一个每个点为原点建立直角坐标系,将其均分为八个象限
以单个象限 R1 为例:假设以点 A 原点,建立直角坐标系,并在 R1 区域内找任意两点 B(x1,y1)、C(x2,y2),且使得 |AB|<=|BC|
根据 A、B、C 坐标可得:|AB|=x1+y1,|AC|=x2+y2,|BC|=|x1-x2|+|y1-y2|
由于 B、C 都在 R1 区域内,那么 y-x>0 且 x>0,分情况讨论有:
- x1>x2 且 y1>y2 时:|AB|>|BC|,与假设矛盾
- x1>x2 且 y1<=y2 时:|BC|=x1-x2+y2-y1,|AC|-|BC|=x2+y2-x1+x2-y2+y1=y1-x1+2*x2,由于 y1>y2>x2>x1,,假设 |AC|<|BC|,则有:x1>2*x2+y1,那么 |AB|=x1+y1>2*x2+2*y1,|AC|=x2+y2<2*x2<|AB|,与前提矛盾,故:|AC|≥|BC|
- x1<=x2 且 y1>y2 时:|BC|=x2-x1+y1-y2,|AC|-|BC|=x2+y2-x2+x1-y1+y2=x1-y1+2*y2,由于 y1>y2>x2>x1,假设 |AC|<|BC|,则有:y1>2*y2+x1,那么 |AB|=x1+y1>2*x1+2*y2,|AC|=x2+y2<2*y2<|AB|,与前提矛盾,故:|AC|≥|BC|
- x1<=x2 且 y1<=y2 时:由于 |AB|+|BC|=|AC|,因此有 |AC|>=|BC|
综上,有 |AC|>=|BC|,推广到 8 个象限中可得:以一个每个点为原点建立直角坐标系,将其均分为八个象限,在每象限内只会向距离该点最近的一个点连边。
根据上述结论,可以筛选出每个区域中最近的点。
由于边的双向性,因此只需要针对一个点考虑 R1~R4 四个方向即可,这样总共最多有 4*n 条边,此时再使用 Kruskal 时,时间复杂度仅为 O(nlogn)
考虑点 (x,y) 在 R1~R4 四个象限的条件:
- 若 (x,y) 在 R1,则:x≥xi,y–x≥yi–xi(最近点的 x+y 最小)
- 若 (x,y) 在 R2,则:y≥yi,y–x≤yi–xi(最近点的 x+y 最小)
- 若 (x,y) 在 R3,则:y≤yi,y+x≥yi+xi(最近点的 y–x 最小)
- 若 (x,y) 在 R4,则:x ≥xi,y+x yi–xi(最近点的 y–x 最小)
因此,利用其中一个条件用排序,另一个条件用树状数组或离散化来维护,从而询问找最近点
【实现】
struct BIT{//树状数组
int arr[N];
int Id[N];
void init(){
memset(arr,INF,sizeof(arr));
memset(Id,-1,sizeof(Id));
}
int lowbit(int k){
return k&(-k);
}
void update(int pos,int val,int id){
while(pos>0){
if(arr[pos]>val){
arr[pos]=val;
Id[pos]=id;
}
pos-=lowbit(pos);
}
}
int query(int pos,int m){
int minval=INF;
int ans=-1;
while(pos<=m){
if(minval>arr[pos]){
minval=arr[pos];
ans=Id[pos];
}
pos+=lowbit(pos);
}
return ans;
}
}B;
struct POS{//区域
int x,y;
int id;
bool operator<(const POS &rhs) const{
if(x!=rhs.x)
return x<rhs.x;
return y<rhs.y;
}
}pos[N];
struct Edge{
int x,y;
int dis;
bool operator<(const Edge &rhs)const {
return dis<rhs.dis;
}
}edge[N<<2],resEdge[N<<2];
int edgeTot,resEdgeTot;
int father[N];
void build(int n){//在R1区域中建边
sort(pos,pos+n);
int a[N],b[N];
for(int i=0;i<n;i++){
a[i]=pos[i].y-pos[i].x;
b[i]=pos[i].y-pos[i].x;
}
//离散化
sort(b,b+n);
int num=unique(b,b+n)-b;
B.init();
for(int i=n-1;i>=0;i--){
int poss=lower_bound(b,b+num,a[i])-b+1;
int ans=B.query(poss,num);
if(ans!=-1){//建边
edge[edgeTot].x=pos[i].id;
edge[edgeTot].y=pos[ans].id;
edge[edgeTot].dis=abs(pos[i].x-pos[ans].x)+abs(pos[i].y-pos[ans].y);//曼哈顿距离
edgeTot++;
}
B.update(poss,pos[i].x+pos[i].y,i);
}
}
void manhattan(int n,int k) {
for(int dir=1;dir<=4;dir++){//左侧四个区域
if(dir==2||dir==4){//变换区域
for(int i=0;i<n;i++)
swap(pos[i].x,pos[i].y);
}
else if(dir==3){//变换区域
for(int i=0;i<n;i++)
pos[i].x=-pos[i].x;
}
build(n);//建边
}
}
int Find(int x) {
return father[x]==x?x:father[x]=Find(father[x]);
}
void kruskal(int n,int k){
resEdgeTot=0;
for(int i=0;i<=n;i++)
father[i]=i;
sort(edge,edge+edgeTot);
int cnt=n-k;
for(int i=0;i<edgeTot;i++) {
int x=edge[i].x,y=edge[i].y;
int fx=Find(x),fy=Find(y);
if(fx!=fy) {
cnt--;
father[fx]=fy;
resEdge[resEdgeTot].x=x;
resEdge[resEdgeTot].y=y;
resEdge[resEdgeTot].dis=edge[i].dis;
resEdgeTot++;
}
}
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
edgeTot=0;
resEdgeTot=0;
for(int i=0;i<n;i++){
scanf("%d%d",&pos[i].x,&pos[i].y);
pos[i].id=i;
}
manhattan(n,k);
kruskal(n,k);
sort(resEdge,resEdge+resEdgeTot);
int res=0;
for(int i=0;i<resEdgeTot;i++)
res+=resEdge[i].dis;
printf("MST=%d\n",res);
printf("The k-th edge's dis is:%d\n",resEdge[resEdgeTot-k].dis);
return 0;
}