BZOJ2177 曼哈顿最小生成树 <树状数组优化建边>

Problem

曼哈顿最小生成树

Time  Limit:  10  Sec\mathrm{Time\;Limit:\;10\;Sec}
Memory  Limit:  256  MB\mathrm{Memory\;Limit:\;256\;MB}

Description

平面坐标系xOy\mathrm{xOy}内,给定nn个顶点V=(x,y)V=(x,y)。对于顶点u,  vu,\;vuuvv之间的距离dd定义为xuxv+yuyv|x_u-x_v|+|y_u-y_v|。你的任务是求出这nn个顶点的最小生成树。

Input

第一行一个正整数nn,表示定点个数。
接下来nn行每行两个正整数x,  yx,\;y,描述一个顶点。

Output

只有一行,为最小生成树的边的距离和。

Sample Input

1
2
3
4
5
4
1 0
0 1
0 -1
-1 0

Sample Output

1
6

HINT

对于100%100\%的数据,n5×104,  0x,y105n\le5\times10^4,\;0\le x, y\le10^5

标签:树状数组 MST

Solution

MMST\mathrm{MMST}裸题。

对于曼哈顿最小生成树,直接建边肯定是不行的,考虑优化建边,去掉一些一定不会在MST\mathrm{MST}中的边。
考虑一个点A(x1,y1)A(x_1,y_1),以AA为中心将整个图分为88个部分。

![0.PNG](https://i.loli.net/2018/03/30/5abe2e2a1e453.png)

对于一个在右上角的点B(x2,y2)B(x_2,y_2),一定有x2x1<y2y1x2>x1x_2-x_1<y_2-y_1且x_2>x_1,若其使x2+y2x1y1x_2+y_2-x_1-y_1最小,则所有在右上角的点的曼哈顿距离都没有ABA\to B小。因此在AA右上角的所有点中,只连ABAB即可。同理在AA周围的八个方向中,每个方向只需连曼哈顿距离最小的点。

对于如何寻找这样的点,拿找右上角的最近点做例子:
最近的点B(x2,y2)B(x_2,y_2)一定使x2+y2x1y1x_2+y_2-x_1-y_1最小,即使x2+y2x_2+y_2最小。同时要满足x2x1<y2y1x2>x1x_2-x_1<y_2-y_1和x_2>x_1,即满足x2y2<x1y1x2>x1x_2-y_2<x_1-y_1且x_2>x_1。可以发现这就是以xx为第一维,xyx-y为第二维做偏序,找到符合的位置中的最小值,用排序+树状数组排序+树状数组维护。
这样我们就可以连每个点到其右上角的最近点的边了。考虑到边可以每次都连双向,因此每个点只用枚举一半即可。这里默认向yy坐标比当前点大的点连边。其实是可以把每种情况都转化为右上角的。
一开始的时候,我们总共需要考虑的是下图区域1,2,3,41,2,3,4中的最近点。第一次连边将11中的最近点连边。

![1.PNG](https://i.loli.net/2018/03/30/5abe2f41874f2.png)

接下来将每个点的x,yx,y坐标互换,即关于直线y=xy=x对称,可以得到下图。第二次连边将22中的最近点连边。

![2.PNG](https://i.loli.net/2018/03/30/5abe2f40e03f2.png)

然后将每个点的xx坐标取反,即可得到下图。这时第三次连边将33中的最近点连边。

![3.PNG](https://i.loli.net/2018/03/30/5abe2f40e1a74.png)

最后再次互换每个点的x,yx,y坐标,得到下图。第四次连边将44中的最近点连边。

![4.PNG](https://i.loli.net/2018/03/30/5abe2f40e3226.png)

由此,我们可以不改回原值就将88个方向连边。
建图后,直接跑Kruskal\mathrm{Kruskal}Prim\mathrm{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;
}