POJ - 3241 Object Clustering (曼哈顿最小生成树)

题意:

平面上有n个点集,现在把他们分成k个集合,使得每个集合中的每个点都至少有一个本集合的点之间的曼哈顿距离不大于X,求最小的X。

分析:

题目要求划分集合之后,每个集合的曼哈顿最小生成树的最长边不超过X,那么容易想到就是整个点集的曼哈顿最小生成树的第n-k条边。

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

  如果直接两两点建边,总边数则为O(n^2)条,用时间复杂度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)满足x1≥x0 且y1−x1>y0−x0。那么B就在A的R1上。

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

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

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

//#include<bits/stdc++.h>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>

#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
#define pii pair<int,int>
#define pdd pair<double,double>
#define pll pair<long long,long long>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define rush() int T;scanf("%d",&T);while(T--)
#define sc(a) scanf("%d",&a)
#define sc2(a,b) scanf("%d%d",&a,&b)
#define sc3(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define pf(a) printf("%d\n",a)
#define pf2(a,b) printf("%d %d\n",a,b)
#define pf3(a,b,c) printf("%d %d %d\n",a,b,c)
#define debug(x) cout<<#x<<": "<<x<<endl
#define all(x) (x).begin(),(x).end()
#define PI acos(-1.0)
#define E exp(1.0)
#define ll long long
#define ld long double
#define ull unsigned long long
//#define io
using namespace std;

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

int n,m,k;

const int maxn=1e4+10;

struct point{
    int x,y,id;
    bool operator < (const point& p) const{
        if(x==p.x) return y<p.y;
        return x<p.x;
    }
}p[maxn];

struct BIT{
    int pos,w;
    void init(){
        pos=-1;w=inf;
    }
}bit[maxn];

struct Edge{
    int u,v,w;
    bool operator <(const Edge& p) const{
        return w<p.w;
    }
}edge[maxn*100];

int tot;
int a[maxn],b[maxn];
int fa[maxn];

inline int lowbit(int x){
    return x&(-x);
}

inline void addedge(int u,int v,int w){
    edge[++tot]=Edge{u,v,w};
}

int query(int x){
    int ans=inf,pos=-1;
    for(int i=x;i<=m;i+=lowbit(i)){
        if(bit[i].w<ans){
            ans=bit[i].w;
            pos=bit[i].pos;
        }
    }
    return pos;
}

void update(int x,int y,int pos){
    for(int i=x;i;i-=lowbit(i)){
        if(y<bit[i].w){
            bit[i].w=y;
            bit[i].pos=pos;
        }
    }
}

inline int dist(point a,point b){
    return abs(a.x-b.x)+abs(a.y-b.y);
}

void solve(){
    sort(p+1,p+1+n);
    rep(i,1,n) a[i]=b[i]=p[i].y-p[i].x;
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
    rep(i,1,n) a[i]=lower_bound(b+1,b+1+m,a[i])-b;
    rep(i,1,m) bit[i].init();
    per(i,n,1){
        int pos=query(a[i]);
        if(pos!=-1){
            addedge(p[i].id,p[pos].id,dist(p[i],p[pos]));
        }
        update(a[i],p[i].x+p[i].y,i);
    }
}

int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}

void kruskal(){
    sort(edge+1,edge+1+tot);
    rep(i,1,n) fa[i]=i;
    int cnt=0;
    rep(i,1,tot){
        int u=find(edge[i].u),v=find(edge[i].v);
        if(u!=v){
            cnt++;
            fa[u]=v;
        }
        if(cnt==k){
            pf(edge[i].w);
            return;
        }
    }
}

int main(){
    #ifdef io
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif

    sc2(n,k);
    k=n-k;
    rep(i,1,n) sc2(p[i].x,p[i].y),p[i].id=i;
    rep(i,0,3){
        if(i==1||i==3){
            rep(j,1,n) swap(p[j].x,p[j].y);
        }else if(i==2) rep(j,1,n) p[j].x=-p[j].x;
        solve();
    }
    kruskal();
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值