图论 —— 生成树 —— 曼哈顿距离最小生成树

【概述】

当给出一些二维平面的点时,记两点间距离为曼哈顿距离,此时的最小生成树,称为曼哈顿最小距离生成树。

对于 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;
}

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值