一、曼哈顿距离最小生成树
曼哈顿距离最小生成树问题可以简述如下:
给定二维平面上的N个点,在两点之间连边的代价为其曼哈顿距离,求使所有点连通的最小代价。
朴素的算法可以用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)的复杂度内解决了。
二、莫队算法
据说这个算法是莫涛提出的(Orz!),但是在网上到处都搜不到相关资料,最后问pty才知道的。这个算法是用于处理一类不带修改的区间查询问题的离线算法,其核心在于利用曼哈顿距离最小生成树算法对区间处理顺序进行处理。比如下面这个例题(清橙A1206《小Z的袜子》,就是莫队出的题):
给定一个长为N的序列,每个元素的值是其颜色。有M次询问,每次询问从一个区间中随机选取两个元素同色的概率。
一次询问[l,r]的答案即,其中是区间中第i中颜色的个数。显然暴力是O(NM)的,而且一般的区间问题的思路似乎不适用。
我们先考虑一个简化的问题:所有的查询区间的左端点都是1。那么我们可以按右端点排序,假设已经处理出了[1,r]的答案,考虑转移到[1,r+k],即添加k个元素,这个可以在O(k)的复杂度内求出。那么处理所有区间的复杂度(不考虑排序)就是O(N)。
那么如果是从[l,r]转移到[l’,r’]呢?复杂度即O(|r’-r|+|l’-l|),也即点(l,r)到点(l’,r’)的曼哈顿距离。那么如果将所有询问转化成二维平面中的点,求曼哈顿距离最小生成树,再按照生成树的顺序做,就可以最小化区间之间转移的复杂度。可以证明(我不会证……似乎莫队的论文里有),这样做的复杂度是O(N1.5)的。问题也就得到了解决。
代码:
POJ3241 Object Clustering 求曼哈顿距离最小生成树上第k大的边
//POJ3241; Object Clustering; Manhattan Distance MST #include <cstdio> #include <cstdlib> #include <algorithm> #define N 100000 #define INFI 123456789 struct point { int x, y, n; bool operator < (const point &p) const { return x == p.x ? y < p.y : x < p.x; } }p[N + 1]; struct inedge { int a, b, w; bool operator < (const inedge &x) const { return w < x.w; } }e[N << 3 | 1]; struct BITnode { int w, p; }arr[N + 1]; int n, k, tot = 0, f[N + 1], a[N + 1], *l[N + 1], ans; template <typename T> inline T abs(T x) { return x < (T)0 ? -x : x; } int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); } inline bool cmp(int *a, int *b) { return *a < *b; } inline int query(int x) { int r = INFI, p = -1; for (; x <= n; x += x & -x) if (arr[x].w < r) r = arr[x].w, p = arr[x].p; return p; } inline void modify(int x, int w, int p) { for (; x > 0; x -= x & -x) if (arr[x].w > w) arr[x].w = w, arr[x].p = p; } inline void addedge(int a, int b, int w) { ++tot; e[tot].a = a, e[tot].b = b, e[tot].w = w; // printf("%d %d %d\n", a, b, w); } inline int dist(point &a, point &b) { return abs(a.x - b.x) + abs(a.y - b.y); } int main() { //Initialize scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) { scanf("%d%d", &p[i].x, &p[i].y); p[i].n = i; } //Solve for (int dir = 1; dir <= 4; ++dir) { //Coordinate transform - reflect by y=x and reflect by x=0 if (dir == 2 || dir == 4) for (int i = 1; i <= n; ++i) p[i].x ^= p[i].y ^= p[i].x ^= p[i].y; else if (dir == 3) for (int i = 1; i <= n; ++i) p[i].x = -p[i].x; //Sort points according to x-coordinate std::sort(p + 1, p + n + 1); //Discretize for (int i = 1; i <= n; ++i) a[i] = p[i].y - p[i].x, l[i] = &a[i]; std::sort(l + 1, l + n + 1, cmp); /* int cnt = 1; for (int i = 2; i <= n; ++i) if (*l[i] != *l[i - 1]) *l[i - 1] = cnt++; else *l[i - 1] = cnt; *l[n] = cnt; */ for (int i = 1; i <= n; ++i) *l[i] = i; //Initialize BIT for (int i = 1; i <= n; ++i) arr[i].w = INFI, arr[i].p = -1; //Find points and add edges for (int i = n; i > 0; --i) { int pos = query(a[i]); if (pos != -1) addedge(p[i].n, p[pos].n, dist(p[i], p[pos])); modify(a[i], p[i].x + p[i].y, i); } } //Kruskal std::sort(e + 1, e + tot + 1); for (int i = 1; i <= n; ++i) f[i] = i; for (int i = 1, ec = n; ec > k && i <= tot; ++i) if (find(e[i].a) != find(e[i].b)) { f[find(e[i].a)] = find(e[i].b); if (--ec == k) ans = e[i].w; } printf("%d\n", ans); return 0; }
//Tsinsen A1206; 小Z的袜子; Modui Algorithm #include <cstdio> #include <cstdlib> #include <algorithm> #define N 50000 #define Q 50000 #define INFI 123456789 typedef long long ll; struct edge { int next, node; }e[Q << 1 | 1]; int head[N + 1], tot = 0; struct point { int x, y, n; bool operator < (const point &p) const { return x == p.x ? y < p.y : x < p.x; } }qt[Q + 1], p[Q + 1]; struct inedge { int a, b, w; bool operator < (const inedge &x) const { return w < x.w; } }ie[Q << 3 | 1]; int cnt = 0; struct BITnode { int w, p; }arr[Q + 1]; int n, q, col[N + 1], a[Q + 1], *l[Q + 1], f[N + 1], c[N + 1]; ll cur, ans[Q + 1]; bool v[Q + 1]; template <typename T> inline T abs(T x) { return x < (T)0 ? -x : x; } inline int dist(const point &a, const point &b) { return abs(a.x - b.x) + abs(a.y - b.y); } inline void addinedge(int a, int b, int w) { ++cnt; ie[cnt].a = a, ie[cnt].b = b, ie[cnt].w = w; } inline void addedge(int a, int b) { e[++tot].next = head[a]; head[a] = tot, e[tot].node = b; } inline bool cmp(int *a, int *b) { return *a < *b; } inline int query(int x) { int r = INFI, p = -1; for (; x <= q; x += x & -x) if (arr[x].w < r) r = arr[x].w, p = arr[x].p; return p; } inline void modify(int x, int w, int p) { for (; x > 0; x -= x & -x) if (arr[x].w > w) arr[x].w = w, arr[x].p = p; } int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); } inline ll calc(int x) { return (ll)x * (x - 1); } inline void add(int l, int r) { for (int i = l; i <= r; ++i) { cur -= calc(c[col[i]]); cur += calc(++c[col[i]]); } } inline void remove(int l, int r) { for (int i = l; i <= r; ++i) { cur -= calc(c[col[i]]); cur += calc(--c[col[i]]); } } void dfs(int x, int l, int r) { v[x] = true; //Process right bound if (r < qt[x].y) add(r + 1, qt[x].y); else if (r > qt[x].y) remove(qt[x].y + 1, r); //Process left bound if (l < qt[x].x) remove(l, qt[x].x - 1); else if (l > qt[x].x) add(qt[x].x, l - 1); ans[x] = cur; //Moving on to next query for (int i = head[x]; i; i = e[i].next) { if (v[e[i].node]) continue; dfs(e[i].node, qt[x].x, qt[x].y); } //Revert changes //Process right bound if (r < qt[x].y) remove(r + 1, qt[x].y); else if (r > qt[x].y) add(qt[x].y + 1, r); //Process left bound if (l < qt[x].x) add(l, qt[x].x - 1); else if (l > qt[x].x) remove(qt[x].x, l - 1); } ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); } int main() { //Initialize scanf("%d%d", &n, &q); for (int i = 1; i <= n; ++i) scanf("%d", col + i); for (int i = 1; i <= q; ++i) scanf("%d%d", &qt[i].x, &qt[i].y); //Manhattan MST for (int i = 1; i <= q; ++i) p[i] = qt[i], p[i].n = i; for (int dir = 1; dir <= 4; ++dir) { //Coordinate transform if (dir == 2 || dir == 4) for (int i = 1; i <= q; ++i) std::swap(p[i].x, p[i].y); else if (dir == 3) for (int i = 1; i <= q; ++i) p[i].x = -p[i].x; //Sort by x-coordinate std::sort(p + 1, p + q + 1); //Discretize for (int i = 1; i <= q; ++i) a[i] = p[i].y - p[i].x, l[i] = &a[i]; std::sort(l + 1, l + q + 1, cmp); int cnt = 1; for (int i = 2; i <= q; ++i) if (*l[i] == *l[i - 1]) *l[i - 1] = cnt; else *l[i - 1] = cnt++; *l[q] = cnt; //Initialize BIT for (int i = 1; i <= q; ++i) arr[i].w = INFI, arr[i].p = -1; //Find points and add edges for (int i = q; i > 0; --i) { int pos = query(a[i]); if (pos != -1) addinedge(p[i].n, p[pos].n, dist(p[i], p[pos])); modify(a[i], p[i].x + p[i].y, i); } } //Kruskal std::sort(ie + 1, ie + cnt + 1); //Initialize disjoint set for (int i = 1; i <= q; ++i) f[i] = i; //Add edges for (int i = 1; i <= cnt; ++i) if (find(ie[i].a) != find(ie[i].b)) { f[find(ie[i].a)] = find(ie[i].b); addedge(ie[i].a, ie[i].b), addedge(ie[i].b, ie[i].a); } //Modui Algorithm ++c[col[1]]; dfs(1, 1, 1); //Output for (int i = 1; i <= q; ++i) { ll x = ans[i], y = calc(qt[i].y - qt[i].x + 1); if (!x) printf("0/1\n"); else { ll g = gcd(x, y); printf("%I64d/%I64d\n", x / g, y / g); } } return 0; }