题目链接: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)满足x1≥x0 且y1−x1>y0−x0。那么B就在A的R1上。
在A的R1区域内距离A最近的点也即满足条件的点中x+y最小的点。因此我们可以将所有点按x坐标排序,再按y−x离散。
用线段树或者树状数组维护大于当前点的y−x的最小的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; }