平面点曼哈顿最小生成树——POJ 3241 Object Clustering

对应POJ题目:点击打开链接

Object Clustering
Time Limit: 2000MS   Memory Limit: 131072K
Total Submissions: 1697   Accepted: 418

Description

We have (N ≤ 10000) objects, and wish to classify them into several groups by judgement of their resemblance. To simply the model, each object has 2 indexes a and b (ab ≤ 500). The resemblance of object i and object j is defined by dij = |a- aj| + |b- bj|, and then we say i is dij resemble to j. Now we want to find the minimum value of X, so that we can classify the N objects into K (< N) groups, and in each group, one object is at most X resemble to another object in the same group, i.e, for every object i, if i is not the only member of the group, then there exists one object j (i ≠ j) in the same group that satisfies dij ≤ X

Input

The first line contains two integers N and K. The following N lines each contain two integers a and b, which describe a object.

Output

A single line contains the minimum X.

Sample Input

6 2
1 2
2 3
2 2
3 4
4 3
3 1

Sample Output

2

题意:

题目很短,然而我并没有看懂,直接搜了下题意:求平面曼哈顿最小生成树第k大的边的长度。


思路:

        其实是在做线段树离线处理的题目无意中注意到莫队算法(据说可以在O(n^1.5)内解决几乎所有无修改的区间查询问题),而且需要有平面曼哈顿最小生成树为前提知识~故来A此题。

平面曼哈顿最小生成树:点击打开链接 ,极为详细!

        大概知道怎样做之后,来看看细节:对所有点(x, y),以x为第一关键字,y为第二关键字升序排序,然后从后面往前处理每个点(假设为p(x, y))的R1域,怎样寻找p点R1域里跟p点最近的点呢?假设(xi, yi)为R1域的一点,论文里也讲到需要满足x >= xi  &&  yi - xi >= y - x;因为是从后往前处理,所以x >= xi是肯定满足的;所以我们需要维护区间[y-x,  MAX[y-x]](MAX[y-x]表示y-x可能的最大值(因为y-x可能为负数,所以是加上了偏移量的最大值))里面x+y的最小值以及那个点的编号;这里可以用二叉排序树,线段树,树状数组。

        在连好每个点的R1域后,我们需要连完每个点的其他7个域,因为边是无向的,所以如果a点的R1域连的是b,那b点的R5域也是a,其他类似;那就没必要把8个域都做一遍,只需要做R1~R4域就行了,虽然论文讲R1跟R2可以一起做,但我觉得有不妥~所以还是把4个域分别做一遍。完成R1域后,其他3个域可以根据坐标翻转性质把他们翻到R1域,然后使用相同的算法来做。

       怎样旋转呢?连好R1域后,把所有点按直线y = x翻转(此时初始的R2域的到了R1域,初始的R3域的到了R8域,初始的R4域的到了R7域),就可以求R2域了;再把所有点按直线x = 0翻转(此时初始的R3域(之前在R8域)的到了R1域,初始的R4域(之前在R7)的到了R2域),就可以求R3域了;再把所有点按直线y = x翻转(此时初始的R4域(之前在R2域)的到了R1域,就可以求R4域了

        最后到了熟悉的kruskal算法求最小生成树~


#include 
#include 
#include 
#include 
#define N 10010
#define MAX_SIZE 2001 //y-x最大值
#define Right_go 1001 //右移偏移量
#define MIN(x, y) ((x)<(y)?(x):(y))
#define inf 0x7fffffff
using namespace std;
int Size; //总边数(可能含重复边)
int fa[N]; //并查集
int a[MAX_SIZE+5]; //a[i] = a[y-x];
int ID[MAX_SIZE+5]; //ID[i] = ID[y-x],即表示(x, y)的结点编号
//树状数组维护的值c[i]为c[x+y]最小值,CID[x+y]为(x, y)的结点编号
int c[MAX_SIZE+5];
int CID[MAX_SIZE+5];

typedef struct{
	int x, y, id;
}Point;

Point p[N];

bool cmp(Point p1, Point p2)
{
	return p1.x < p2.x;
}

typedef struct{
	int u, v, w;
}Edge;

Edge E[10*N];

void Add(int u, int v, int w)
{
	Size++;
	E[Size].u = u;
	E[Size].v = v;
	E[Size].w = w;
}

void Init()
{
	int i;
	for(i = 1; i <= MAX_SIZE; i++){
		a[i] = c[i] = inf;
		ID[i] = CID[i] = -1;
	}
}

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

void Update(int pos, int val, int id)
{
	int i;
	if(val >= a[pos]) return;
	a[pos] = val;
	ID[pos] = id;
	for(i = pos; i <= MAX_SIZE; i += lowbit(i))
		if(val < c[i]){
			c[i] = val;
			CID[i] = id;
		}
}

void Query(int l, int r, int i)
{
    int ans = inf, id = -1;
    while(true){
		if(a[r] < ans){
			ans = a[r];
			id = ID[r];
		}
        if(r == l) break;
        for(r -= 1; r - l >= lowbit(r); r -= lowbit(r))
			if(c[r] < ans){
				ans = c[r];
				id = CID[r];
			}
    }
	if(ans != inf) Add(id, p[i].id, ans - (p[i].y + p[i].x));
}

void Cal(int n)
{
	sort(p + 1, p + n + 1, cmp);
	int i;
	Init();
	for(i = n; i > 0; i--){
		int pos = p[i].y - p[i].x;
		pos += Right_go;
		Query(pos, MAX_SIZE, i);
		Update(pos, p[i].y + p[i].x, p[i].id);
	}
}

void Swap(int &a, int &b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Solve(int n)
{
	Cal(n);
	int j;
	//每个点坐标沿y = x直线翻转
	for(j = 1; j <= n; j++)
		Swap(p[j].x, p[j].y);
	Cal(n);
	//每个点坐标沿x = 0直线翻转
	for(j = 1; j <= n; j++)
		p[j].x = -p[j].x;
	Cal(n);
	//每个点坐标沿y = x直线翻转
	for(j = 1; j <= n; j++)
		Swap(p[j].x, p[j].y);
	Cal(n);
}

bool cmpk(Edge e1, Edge e2)
{
	return e1.w < e2.w;
}

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

void kruskal(int n, int k)  
{
	sort(E + 1, E + Size + 1, cmpk);
	int cnt = 1, j = 1;  
    int u1, v1, c1, c2;  
    for(int i = 1; i <= n; i++) fa[i] = i;  
    while(cnt <= n - k)
    {  
        u1=E[j].u;  
        v1=E[j].v;  
        c1=find(u1);  
        c2=find(v1);  
        if(c1!=c2){//u1与v1属于不同的集合  
            fa[c1]=c2;//合并  
			cnt++;
        }  
        j++;  
    }  
    printf("%d\n", E[j-1].w);  
}

int main()
{
	//freopen("in.txt","r",stdin);
	int n, k;
	while(scanf("%d%d", &n, &k) == 2){
		Size = 0;
		int i, j;
		for(i = 1; i <= n; i++){
			scanf("%d%d", &p[i].x, &p[i].y);
			p[i].id = i;
		}
		Solve(n);
		kruskal(n, k);
	}
	return 0;
}







你可能感兴趣的:(树)