Problem
曼哈顿最小生成树
TimeLimit:10Sec
MemoryLimit:256MB
Description
平面坐标系xOy内,给定n个顶点V=(x,y)。对于顶点u,v,u与v之间的距离d定义为∣xu−xv∣+∣yu−yv∣。你的任务是求出这n个顶点的最小生成树。
第一行一个正整数n,表示定点个数。
接下来n行每行两个正整数x,y,描述一个顶点。
Output
只有一行,为最小生成树的边的距离和。
Sample Output
HINT
对于100%的数据,n≤5×104,0≤x,y≤105。
标签:树状数组
MST
Solution
MMST裸题。
对于曼哈顿最小生成树,直接建边肯定是不行的,考虑优化建边,去掉一些一定不会在MST中的边。
考虑一个点A(x1,y1),以A为中心将整个图分为8个部分。
![0.PNG](https://i.loli.net/2018/03/30/5abe2e2a1e453.png)
对于一个在右上角的点B(x2,y2),一定有x2−x1<y2−y1且x2>x1,若其使x2+y2−x1−y1最小,则所有在右上角的点的曼哈顿距离都没有A→B小。因此在A右上角的所有点中,只连AB即可。同理在A周围的八个方向中,每个方向只需连曼哈顿距离最小的点。
对于如何寻找这样的点,拿找右上角的最近点做例子:
最近的点B(x2,y2)一定使x2+y2−x1−y1最小,即使x2+y2最小。同时要满足x2−x1<y2−y1和x2>x1,即满足x2−y2<x1−y1且x2>x1。可以发现这就是以x为第一维,x−y为第二维做偏序,找到符合的位置中的最小值,用排序+树状数组维护。
这样我们就可以连每个点到其右上角的最近点的边了。考虑到边可以每次都连双向,因此每个点只用枚举一半即可。这里默认向y坐标比当前点大的点连边。其实是可以把每种情况都转化为右上角的。
一开始的时候,我们总共需要考虑的是下图区域1,2,3,4中的最近点。第一次连边将1中的最近点连边。
![1.PNG](https://i.loli.net/2018/03/30/5abe2f41874f2.png)
接下来将每个点的x,y坐标互换,即关于直线y=x对称,可以得到下图。第二次连边将2中的最近点连边。
![2.PNG](https://i.loli.net/2018/03/30/5abe2f40e03f2.png)
然后将每个点的x坐标取反,即可得到下图。这时第三次连边将3中的最近点连边。
![3.PNG](https://i.loli.net/2018/03/30/5abe2f40e1a74.png)
最后再次互换每个点的x,y坐标,得到下图。第四次连边将4中的最近点连边。
![4.PNG](https://i.loli.net/2018/03/30/5abe2f40e3226.png)
由此,我们可以不改回原值就将8个方向连边。
建图后,直接跑Kruskal或Prim即可。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include <bits/stdc++.h> #define MAX_N 100000 #define INF 4020010910LL using namespace std; typedef long long lnt; template <class T> inline void read(T &x) { x = 0; int c = getchar(), f = 1; for (; !isdigit(c); c = getchar()) if (c == 45) f = -1; for (; isdigit(c); c = getchar()) (x *= 10) += f*(c-'0'); } int n, cnt, fa[MAX_N+5]; lnt tr[MAX_N+5]; int mi[MAX_N+5]; struct edge {int u, v; lnt c;} E[(MAX_N<<2)+5]; struct P {int id; lnt x, y;} p[MAX_N+5], q[MAX_N+5]; bool cmpe (const edge &a, const edge &b) {return a.c < b.c;} bool cmpp (const P &a, const P &b) {return a.x == b.x ? a.y < b.y : a.x < b.x;} lnt dist(int u, int v) {return abs(q[u].x-q[v].x)+abs(q[u].y-q[v].y);} void addedge(int u, int v) {E[++cnt] = (edge){u, v, dist(u, v)};} int getf(int x) {return fa[x] == x ? x : fa[x] = getf(fa[x]);} void modify(int pos, lnt x, int id) { for (; pos; pos -= pos&-pos) if (tr[pos] > x) tr[pos] = x, mi[pos] = id; } int query(int pos) { int ret = -1; lnt mc = INF; for (; pos <= n; pos += pos&-pos) if (tr[pos] < mc) mc = tr[pos], ret = mi[pos]; return ret; } void build() { int a[MAX_N+5], b[MAX_N+5], m; sort(p+1, p+n+1, cmpp); for (int i = 1; i <= n; i++) a[i] = b[i] = p[i].y-p[i].x; sort(b+1, b+n+1), m = (int)(unique(b+1, b+n+1)-b-1); for (int i = 1; i <= n; i++) tr[i] = INF; memset(mi, -1, sizeof mi); for (int i = n, rk, mp; i; i--) { rk = (int)(lower_bound(b+1, b+m+1, a[i])-b); mp = query(rk); if (~mp) addedge(p[i].id, mp); modify(rk, p[i].x+p[i].y, p[i].id); } } lnt MST() { sort(E+1, E+cnt+1, cmpe); lnt ret = 0; for (int i = 1; i <= n; i++) fa[i] = i; for (int i = 1, u, v; i <= cnt; i++) { u = getf(E[i].u), v = getf(E[i].v); if (u^v) fa[u] = v, ret += E[i].c; } return ret; } int main() { read(n); for (int i = 1; i <= n; i++) p[i].id = i, read(p[i].x), read(p[i].y), q[i] = p[i]; build(); for (int i = 1; i <= n; i++) swap(p[i].x, p[i].y); build(); for (int i = 1; i <= n; i++) p[i].x *= -1; build(); for (int i = 1; i <= n; i++) swap(p[i].x, p[i].y); build(); for (int i = 1; i <= n; i++) p[i].y *= -1; return printf("%lld\n", MST()), 0; }
|