曼哈顿距离最小生成树

一、前人种树

博客:曼哈顿距离最小生成树与莫队算法

博客:学习总结:最小曼哈顿距离生成树

 

二、知识梳理

曼哈顿距离:给定二维平面上的N个点,在两点之间连边的代价。(即distance(P1,P2) = |x1-x2|+|y1-y2|)

曼哈顿距离最小生成树问题求什么?求使所有点连通的最小代价。

最小生成树的“环切”性质:在图G = (V, E)中,如果存在一个环,那么把环上的最大边e删除后得到的图G’ = (V, E- {e})的最小生成树的边权和与G相同。

 

三、难点剖析

【废话定理神马的,很难懂只要记住就是了】

朴素的算法可以用O(N2)的Prim,或者处理出所有边做Kruskal,但在这里总边数有O(N2)条,所以Kruskal的复杂度变成了O(N2logN)。  

但是事实上,真正有用的边远没有O(N2)条。我们考虑每个点会和其他一些什么样的点连边。

可以得出这样一个结论:以一个点为原点建立直角坐标系,在每45度内只会向距离该点最近的一个点连边。

证明结论:假设我们以点A为原点建系,考虑在y轴向右45度区域内的任意两点B(x1,y1)和C(x2,y2),不妨设|AB|≤|AC|(这里的距离为曼哈顿距离),如下图:

|AB|=x1+y1,|AC|=x2+y2,|BC|=|x1-x2|+|y1-y2|。而由于B和C都在y轴向右45度的区域内,有y-x>0且x>0。下面我们分情况讨论:

  1. x1>x2且y1>y2。这与|AB|≤|AC|矛盾;
  2. 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|;
  3. x1>x2且y1≤y2。与2同理;
  4. x1≤x2且y1≤y2。此时显然有|AB|+|BC|=|AC|,即有|AC|>|BC|。

综上有|AC|≥|BC|,也即在这个区域内只需选择距离A最近的点向A连边。

这种连边方式可以保证边数是O(N)的,那么如果能高效处理出这些边,就可以用Kruskal在O(NlogN)的时间内解决问题。下面我们就考虑怎样高效处理边。

我们只需考虑在一块区域内的点,其他区域内的点可以通过坐标变换“移动”到这个区域内。为了方便处理,我们考虑在y轴向右45度的区域。在某个点A(x0,y0)的这个区域内的点B(x1,y1)满足x1≥x0且y1-x1>y0-x0。这里对于边界我们只取一边,但是操作中两边都取也无所谓。那么|AB|=y1-y0+x1-x0=(x1+y1)-(x0+y0)。在A的区域内距离A最近的点也即满足条件的点中x+y最小的点。因此我们可以将所有点按x坐标排序,再按y-x离散,用线段树或者树状数组维护大于当前点的y-x的最小的x+y对应的点。时间复杂度O(NlogN)。

至于坐标变换,一个比较好处理的方法是第一次直接做;第二次沿直线y=x翻转,即交换x和y坐标;第三次沿直线x=0翻转,即将x坐标取相反数;第四次再沿直线y=x翻转。注意只需要做4次,因为边是双向的。

至此,整个问题就可以在O(NlogN)的复杂度内解决了。

【回到正题】

一个点把平面分成了8个部分:

由上面的废话可知,我们只需要让这个点与每个部分里距它最近的点连边。

拿R1来说吧:

如图,i的R1区域里距i最近的点是j。也就是说,其他点k都有:

xj + yj <= xk + yk

那么k将落在如下阴影部分:

显然,边(i,j), (j,k), (i,k)构成一个环<i,j,k>,而(i,k)一定是最长边,可以被删去。所以我们只连边(i,j)。

为了避免重复加边,我们只考虑R1~R4这4个区域。(总共加了4N条边)

这4个区域的点(x,y)要满足什么条件?

  • 如果点(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最小)

其中一个条件用排序,另一个条件用数据结构(这种方法很常用),在数据结构上询问,找最近点。因为询问总是前缀或后缀,所以可以用树状数组。

转载:https://www.cnblogs.com/xzxl/p/7237246.html

自己的代码:Another Minimum Spanning Tree UVALive - 3662 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define lowbit(x) x&(-x)
typedef long long ll;
const int N=100100;
struct Point{
	int x,y;
	int y_x;
	int id;
	bool operator <(const Point &xx)const
	{
		if(x!=xx.x) return x<xx.x;
		else return y<xx.y;
	}
}p[N];
int n,k;
int b[N],len;
int sum[N],posid[N];
// 树状数组维护 大于等于(y-x) 最小的(x+y) 的对应的编号 
void update(int x,int d,int id)
{
	while(x)
	{
		if(d<sum[x])
		{
			sum[x]=d;
			posid[x]=id;
		}
		x-=lowbit(x); 
	}
}
int query(int x)
{
	int minn=INF,id=-1;
	while(x<=len)
	{
		if(sum[x]<minn)
		{
			minn=sum[x];
			id=posid[x];
		}
		x+=lowbit(x);
	}
	return id;
}
int dis(Point x,Point y)
{
	return abs(x.x-y.x)+abs(x.y-y.y);
}
struct edge{
	int u,v,d;
	bool operator <(const edge &x)const
	{
		return d<x.d;
	}
}e[N*4];
int tot;
void addedge(int x,int y,int d)  // 记录储存的边 
{
	tot++;
	e[tot].u=x;
	e[tot].v=y;
	e[tot].d=d;
}
int f[N];
int fath(int x)
{
	return x==f[x]?x:f[x]=fath(f[x]);
}
void init()
{
	for(int i=1;i<=n;i++)
	{
		f[i]=i;
	}
	tot=0;
}
int main()
{
	int ca=1;
	int pos,cnt;
	int x,y;
	ll ans;
	while(~scanf("%d",&n) && n)
	{
		init();
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&p[i].x,&p[i].y);
			p[i].id=i;
		}
		
		for(int i=0;i<4;i++)
		{
			if(i==1||i==3)
			{
				for(int j=1;j<=n;j++)
					swap(p[j].x,p[j].y);
			}
			else if(i==2)
			{
				for(int j=1;j<=n;j++)
					p[j].x=-p[j].x;
			}
			for(int j=1;j<=n;j++)
			{
				p[j].y_x=p[j].y-p[j].x;
				b[j]=p[j].y_x;	
			}
			// 离散化 (y-x) 
			sort(b+1,b+1+n);
			len=unique(b+1,b+1+n)-(b+1);
			
			sort(p+1,p+1+n);
			for(int j=1;j<=len;j++)sum[j]=INF;
			for(int j=n;j>=1;j--)
			{
				pos=lower_bound(b+1,b+1+len,p[j].y_x)-b;
				cnt=query(pos);
				if(cnt!=-1) addedge(p[j].id,p[cnt].id,dis(p[j],p[cnt])); // 加边 
				update(pos,p[j].x+p[j].y,j);
			}
		}
		sort(e+1,e+1+tot);
		cnt=0;
		ans=0;   // 求生成树距离和 
		for(int i=1;i<=tot;i++)
		{
			x=fath(e[i].u);
			y=fath(e[i].v);
			if(x==y) continue;
			f[x]=y;
			cnt++;
			ans+=1LL*e[i].d;
			if(cnt==n-1)
			{
				
				break;
			}
		}
		printf("Case %d: Total Weight = %lld\n",ca++,ans);
	}
	return 0;
} 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值