转自:https://www.cnblogs.com/xzxl/p/7237246.html
曼哈顿距离最小生成树
一、前人种树
二、知识梳理
曼哈顿距离:给定二维平面上的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。下面我们分情况讨论:
- x1>x2且y1>y2。这与|AB|≤|AC|矛盾;
- 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|;
- x1>x2且y1≤y2。与2同理;
- 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最小)
其中一个条件用排序,另一个条件用数据结构(这种方法很常用),在数据结构上询问,找最近点。因为询问总是前缀或后缀,所以可以用树状数组。
POJ3241Object Clustering
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
struct edge
{
int x, y, v;
bool operator < (const edge &tmp) const
{
return v < tmp.v;
}
} E[N<<3];
struct point
{
int x, y, id;
bool operator < (const point &tmp) const
{
return x == tmp.x ? y < tmp.y : x < tmp.x;
}
} P[N];
struct lsh
{
int id, a;
bool operator < (const lsh &tmp) const
{
return a < tmp.a;
/* if (a == tmp.a) return id < tmp.id;
return a < tmp.a;
*/
}
} LSH[N];
int A[N], F[N];
int MI[N], ID[N];
int n, c, sz, tot, cnt;
int lowbit (int x)
{
return x&(-x);
}
int query(int x)
{
int ans = -1, mi = inf;
for (; x <= n; x += lowbit(x))
if (MI[x] < mi)
{
mi = MI[x];
ans = ID[x];
}
return ans;
}
void modify(int x, int mi, int id)
{
for (; x > 0; x -= lowbit(x))
if (MI[x] > mi)
{
MI[x] = mi;
ID[x] = id;
}
}
//BIT维护的是某数字代表的区间的X+Y最小值, 若一区间的不同位置最小值不同, 该区间则没有最小值(即MI数组维护的是其表示的区间都可以取到的最小值)
int find(int x)
{
return F[x] == x ? x : F[x] = find(F[x]);
}
void join(int x, int y)
{
int fx = find(x), fy = find(y);
if (fx == fy) return;
F[fx] = fy;
cnt++;
}
void init ()
{
sort(P + 1, P + n + 1);
for (int i = 1; i <= n; ++i)
{
LSH[i].a = P[i].y - P[i].x;
LSH[i].id = i;
MI[i] = inf;
ID[i] = -1;
}
}
int abs(int x, int y)
{
return x > 0 ? x : -x;
}
int dts(int x, int y)
{
return abs(P[x].x - P[y].x) + abs(P[x].y -P[y].y);
}
void add_edge (int x, int y, int d)
{
E[++sz].x = x;
E[sz].y = y;
E[sz].v = d;
}
int main()
{
scanf("%d%d", &n, &c);
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", &P[i].x, &P[i].y);
P[i].id = i;
}
for (int cas = 1; cas <= 4; ++cas)
{
if (cas == 2 || cas == 4)
for (int i = 1; i <= n; ++i)
swap(P[i].x, P[i].y);
if (cas == 3)
for (int i = 1; i <= n; ++i)
P[i].x = -P[i].x;
init();
sort(LSH + 1, LSH + n + 1);//按Y-X离散化
for (int i = 1; i <= n; ++i)
A[LSH[i].id] = i; //A表示某点在BIT中的位置
for (int i = n; i >= 1; --i)
{
int tmp = query(A[i]);
if (tmp != -1)
add_edge(P[tmp].id, P[i].id, dts(tmp, i));
modify(A[i], P[i].x + P[i].y, i);
}
}
for (int i = 1; i <= n; ++i) F[i] = i;
sort(E + 1, E + sz + 1);
for (int i = 1; i <= sz; ++i)
{
join(E[i].x, E[i].y);
if (cnt == n - c)
{
printf("%d\n", E[i].v);
break;
}
}
return 0;
}