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

题意:给出n个点,先要将n个点分为k个集合,要求对于每一个元素,都能在同一集合内找到至少一个元素满足两者之间的曼哈顿距离小于X,问最小的x是多少

解法:显然是求最小生成树上第k长的边,由于是完全图,因此要充分利用曼哈顿距离的特点求解。

定理:可以证明每一个顶点在最小生成树中在这45度的范围内至多仅有一条边与之相连。

    仔细分析这一性质,我们猜想,这一条边是否一定是这个顶点在这一方向上的最小边?事实上,可以证明这一猜想是正确的。证明方法是假设有其他顶点与这一顶点有边相连,则有a与b相连,a与c相连。其中ab距离大于等于ac距离,分类讨论得知bc距离一定小于等于ab距离,可以将ab边换为ac边得到的新生成树不会比原先的差。

我们只要求一个点在其45°角的区域内离他最近的点就行了,而这可以用线段树或树状数组解决

     因此可以在建图时每一个顶点仅连出8条边,这样图中边数至多为点数的4倍,采用kruskal算法可在时限内出解。

我们以y轴正半轴往右偏45°角的区域为例:

假设原点坐标为(x,y),其他点的坐标是(xi,yi),那么就是要求:

min{ yi+xi - (y+x) } 要满足的条件:yi>=y,xi>=x,yi-xi<=y-x。

如果满足了yi>=y,yi-xi<=y-x 那么就一定满足 xi>=x。

    因此可以将所有点按y-x为关键字排序后将x+y逐一插入到线段树中,每次查询y右侧x+y最小的点,如果存在这样的点则这两点之间的边可能是最小生成树中的边。

    对于其他区间,排序条件和需要维护的统计量都发生了一些改变,但是为了减少代码量,我们通过交换x和y值或取相反数等坐标转换可以复用第一个区间内的操作。

import java.util.Arrays;
import java.util.Scanner;

public class ManMST {
	int maxn = 10010,maxm=510,inf = 1 << 28;
	//点数 坐标范围
	class node implements Comparable<node> {
		int id,x, y;
		node(int i,int a,int b){
			x=a;
			y=b;
			id=i;
		}
		public int compareTo(node oth) {
			if (y-x<oth.y-oth.x)
				return -1;
			if(y-x==oth.y-oth.x&&y>oth.y)
				return -1;
			return 1;
		}
	}
	SegTree st = new SegTree();
	MST mst = new MST();
	node nos[] = new node[maxn];
	int x[] = new int[maxn], y[] = new int[maxn], n, k;
	void init() {
		n = scan.nextInt();
		k = scan.nextInt();
		for (int i = 0; i < n; i++) {
			x[i] = scan.nextInt();
			y[i] = scan.nextInt();
			nos[i] = new node(i,x[i],y[i]);
		}
		mst.init(n);
	}
	int dis(int i,int j){
		return Math.abs(x[i]-x[j])+Math.abs(y[i]-y[j]);
	}
	void work() {
		Arrays.sort(nos,0,n);
		st.init(1, 1, maxm);
		for (int i=0;i<n; i++) {
			int temp=st.query(nos[i].y, maxm);//查询y--maxm区间内最小值对应的编号
			if(temp!=-1)
				mst.add(nos[i].id,temp,dis(nos[i].id,temp));
			st.update(1, nos[i].y,nos[i].id,nos[i].x+nos[i].y);//更新线段树,插入编号
		}
	}
	void run() {
		init();
		work();
		for(int i=2;i<=4;i++){
			if(i%2==0)
				for(int j=0;j<n;j++)
					nos[j].x=maxm-nos[j].x;
			if(i==3)
			for(int j=0;j<n;j++){
				int temp=nos[j].x;
				nos[j].x=nos[j].y;
				nos[j].y=temp;
			}
			work();
		}
		int ans=mst.solve(k);
		System.out.println(ans);
	}
	Scanner scan = new Scanner(System.in);
	class SegTree {
		class SegNode {
			int mx, id;
			int left, right;
			SegNode(int l, int r) {
				left = l;
				right = r;
				mx = inf;
				id=-1;
			}
			int mid() {
				return (left + right) >> 1;
			}
		}
		SegNode tree[] = new SegNode[maxm*4];
		void init(int idx, int left, int right) {
			tree[idx] = new SegNode(left, right);
			if (left == right)
				return;
			int mid = tree[idx].mid();
			init(idx << 1, left, mid);
			init(idx << 1 | 1, mid + 1, right);
		}

		void update(int idx, int pos, int i, int v) {
			if (tree[idx].left == pos && tree[idx].right == pos) {
				if (v < tree[idx].mx) {
					tree[idx].mx = v;
					tree[idx].id = i;
					return;
				}
				return;
			}
			int mid = tree[idx].mid();
			if (pos <= mid)
				update(idx << 1, pos, i, v);
			else
				update(idx << 1 | 1, pos, i, v);
			if (tree[idx << 1].mx < tree[idx << 1 | 1].mx) {
				tree[idx].mx = tree[idx << 1].mx;
				tree[idx].id = tree[idx << 1].id;
			} else {
				tree[idx].mx = tree[idx << 1 | 1].mx;
				tree[idx].id = tree[idx << 1 | 1].id;
			}
		}
		int query(int left,int right){
			return query(1,left,right).id;
		}
		SegNode query(int idx, int left, int right) {
			if (left == tree[idx].left && right == tree[idx].right) 
				return tree[idx];			
			int mid = tree[idx].mid();
			if (right <= mid)
				return query(idx << 1, left, right);
			else if (left > mid)
				return query(idx << 1 | 1, left, right);
			else
			{    SegNode ll=query(idx << 1, left, mid);
				 SegNode rr=query(idx << 1 | 1, mid + 1, right);
				 if(ll.mx<rr.mx)
					 return ll;
				 else
					 return rr;
			}
		}
	}

	class MST {
		class Edge implements Comparable<Edge> {
			int x, y, val;
			Edge(int a, int b, int v) {
				x = a;
				y = b;
				val = v;
			}
			public int compareTo(Edge oth) {
				if (val < oth.val)
					return -1;
				return 1;
			}
		}
		Edge ed[] = new Edge[maxn*4];
		int cnt, f[] = new int[maxn], n;
		void init(int n) {
			this.n = n;
			cnt = 0;
			for (int i = 0; i <= n; i++)
				f[i] = i;
		}
		void add(int a, int b, int v) {
			ed[cnt++] = new Edge(a, b, v);
		}
		int find(int x) {
			if (f[x] != x)
				f[x] = find(f[x]);
			return f[x];
		}
		int solve(int k) {
			Arrays.sort(ed, 0, cnt);
			for (int i = 0; i < cnt; i++) {
				int a = find(ed[i].x);
				int b = find(ed[i].y);
				if (a == b)
					continue;
				f[a] = b;
				if (--n == k)
					return ed[i].val;
			}
			return -1;
		}
	}
	public static void main(String[] args) {
		new ManMST().run();
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值