poj3241(曼哈顿最小生成树)

题目链接:http://poj.org/problem?id=3241

题意:求曼哈顿距离最小生成树第k大的边。曼哈顿距离:点A(x1,y1)与点B(x2,y2)距离d=abs(x1-x2)+abs(y1-y2)。

求解曼哈顿距离最小生成树的方法简述:

  如果直接两两点建边,总边数则为O(n2)条,用时间复杂度O(N+E)的Prim算法,时间复杂度O(E*logE)的kruskal算法都是会超时的(这里N为点数,E为边数)

  而这里的解题思路就是将边数复杂度降到O(n),因为大量的边是没有用到的,只要每45度方向距离该点最近的一个点连边即可。

  

  证明就不证了,直接上结论,假如B,C都是A 在y轴向右45度区域内并且 |AB|<=|AC|,可证得|BC|<=|AC|,所以|AB|+|BC|<=|AC|+|AB|或|AC|+|BC|,即A,B,C三点距离最短为 每45度方向距离最短之和(这里距离都是曼哈顿距离) 证明博客链接:https://blog.csdn.net/huzecong/article/details/8576908

   依据上面可得只要连接每个点与八个方向距离最小的点连边即可。

   又因为,边是双向的,我们只要连R1,R2,R3,R4四个方向即可。比如:(上图)B是A R1最小距离点,A是B R5最小距离点,我们只要连R1,R5不连,即A连向B即可知道AB的关系。

  思路出来了,那么怎么处理呢?我们先考虑一个方向,例如R1,(其他三个方向类比即可)。

   在某个点A(x0,y0)的这个区域内的点B(x1,y1)满足x1xy1x1>y0x0。那么B就在A的R1上

  在A的R1区域内距离A最近的点也即满足条件的点中x+y最小的点。因此我们可以将所有点按x坐标排序,再按yx离散。

  用线段树或者树状数组维护大于当前点的yx的最小的x+y对应的点(也就是维护区间最小值)这里用的是树状数组,因为代码较简单。

树状数组知识:https://www.cnblogs.com/hsd-/p/6139376.html

   这样R1区域就处理完了,而其他三个区域的点只要用数学知识 翻转和对称 转化成R1处理即可。比如,R2区域将关于y=x对称(即swap(x,y)),再将关于

x轴对称(即x=-x)可求R3,再关于y=x对称求R4。

还有些细节可以看代码:

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1e5;
struct point{
    int x,y,id;
    bool operator < (const point &a)const{
        return a.x==x?y<a.y:x<a.x;
    }
}p[maxn];//按x从小到大排序,再按y-x离散 

struct node{
    int u,v,w;
    bool operator < (const node &a)const{
        return w<a.w;
    }
}edge[maxn<<3];

struct Bit{//树状数组维护区间最小值 
    int pos,w;
    void init(){ pos=-1;w=inf;}
}bit[maxn];

int fa[maxn],a[maxn],b[maxn];//数组a,b用于离散化,求得 在y轴向右45度的所有点 
int n,m,k,cnt;

int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
void add(int u,int v,int w){
    edge[++cnt].u=u;edge[cnt].v=v;edge[cnt].w=w;
}
int lowbit(int x){return x&(-x);}
int dist(point a,point b){return abs(a.x-b.x)+abs(a.y-b.y);}
int query(int x,int m){//查询区间p[i].x+p[i].y最小值 
    int minx=inf,pos=-1;
    for(int i=x;i<=m;i+=lowbit(i)){
        if(minx>bit[i].w){
            minx=bit[i].w;
            pos=bit[i].pos;
        }
    }
    return pos;
}
void update(int x,int y,int pos){//维护区间p[i].x+p[i].y最小值 
    for(int i=x;i>=1;i-=lowbit(i)){
        if(y<bit[i].w){
            bit[i].w=y;
            bit[i].pos=pos;
        }
    }
}
void solve(){//Kruskal求第k大的边 
    int tot=0;
    sort(edge+1,edge+cnt+1);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=cnt;i++){
        if(find(edge[i].u)!=find(edge[i].v)){
            tot++;
            fa[find(edge[i].u)]=find(edge[i].v);
        }
        if(tot==k){
            printf("%d
",edge[i].w);
            break;
        }
    }
}
void caledge(){
    sort(p+1,p+n+1);
    for(int i=1;i<=n;i++)
        a[i]=b[i]=p[i].y-p[i].x;
    sort(b+1,b+n+1);
    m=unique(b+1,b+n+1)-b;//去重 
    for(int i=1;i<=m;i++)//初始化 
        bit[i].init();
    for(int i=n;i>=1;i--){
    //只能从x大的 开始遍历 ,图像从右往左遍历,才能查询和维护R1,R2,R3,R4区域最小距离点 
        int x=lower_bound(b+1,b+m+1,a[i])-b+1;//从y轴向右45度的区域寻找,即b.y-b.x>a.y-a.x 
        int pos=query(x,m);//查找与点p[i]最近的点(y轴向右45度的区域) 
        if(pos!=-1)//查询到就连边 
            add(p[i].id,p[pos].id,dist(p[i],p[pos]));
        update(x,p[i].x+p[i].y,i);//维护区间最小值 
    }
}
int main(){
    scanf("%d%d",&n,&k);
    k=n-k;cnt=0;
    for(int i=1;i<=n;i++)    
        scanf("%d%d",&p[i].x,&p[i].y),p[i].id=i;
    for(int j=0;j<4;j++){
        if(j==1||j==3)
            for(int i=1;i<=n;i++)
                swap(p[i].x,p[i].y);
        else if(j==2)
            for(int i=1;i<=n;i++)
                p[i].x=-p[i].x;
        caledge();
    }
    solve();
    return 0;
}
原文地址:https://www.cnblogs.com/xiongtao/p/10533314.html