【代码超详解】POJ 1984 Navigation Nightmare(带权并查集,110 ms)

一、题目描述

Navigation Nightmare

Time Limit: 2000MS Memory Limit: 30000K
Total Submissions: 8878 Accepted: 3214(2020/3/15 16:00)
Case Time Limit: 1000MS

Description

Farmer John’s pastoral neighborhood has N farms (2 <= N <= 40,000), usually numbered/labeled 1…N. A series of M (1 <= M < 40,000) vertical and horizontal roads each of varying lengths (1 <= length <= 1000) connect the farms. A map of these farms might look something like the illustration below in which farms are labeled F1…F7 for clarity and lengths between connected farms are shown as (n):

       F1 --- (13) ---- F6 --- (9) ----- F3

        |                                 |

       (3)                                |

        |                                (7)

       F4 --- (20) -------- F2            |

        |                                 |

       (2)                               F5

        | 

       F7 

Being an ASCII diagram, it is not precisely to scale, of course.

Each farm can connect directly to at most four other farms via roads that lead exactly north, south, east, and/or west. Moreover, farms are only located at the endpoints of roads, and some farm can be found at every endpoint of every road. No two roads cross, and precisely one path
(sequence of roads) links every pair of farms.

FJ lost his paper copy of the farm map and he wants to reconstruct it from backup information on his computer. This data contains lines like the following, one for every road:

There is a road of length 10 running north from Farm #23 to Farm #17
There is a road of length 7 running east from Farm #1 to Farm #17

As FJ is retrieving this data, he is occasionally interrupted by questions such as the following that he receives from his navigationally-challenged neighbor, farmer Bob:

What is the Manhattan distance between farms #1 and #23?

FJ answers Bob, when he can (sometimes he doesn’t yet have enough data yet). In the example above, the answer would be 17, since Bob wants to know the “Manhattan” distance between the pair of farms.
The Manhattan distance between two points (x1,y1) and (x2,y2) is just |x1-x2| + |y1-y2| (which is the distance a taxicab in a large city must travel over city streets in a perfect grid to connect two x,y points).

When Bob asks about a particular pair of farms, FJ might not yet have enough information to deduce the distance between them; in this case, FJ apologizes profusely and replies with “-1”.

Input

  • Line 1: Two space-separated integers: N and M

  • Lines 2…M+1: Each line contains four space-separated entities, F1,

    F2, L, and D that describe a road. F1 and F2 are numbers of

    two farms connected by a road, L is its length, and D is a

    character that is either ‘N’, ‘E’, ‘S’, or ‘W’ giving the

    direction of the road from F1 to F2.

  • Line M+2: A single integer, K (1 <= K <= 10,000), the number of FB’s

    queries

  • Lines M+3…M+K+2: Each line corresponds to a query from Farmer Bob

    and contains three space-separated integers: F1, F2, and I. F1

    and F2 are numbers of the two farms in the query and I is the

    index (1 <= I <= M) in the data after which Bob asks the

    query. Data index 1 is on line 2 of the input data, and so on.

Output

  • Lines 1…K: One integer per line, the response to each of Bob’s

    queries. Each line should contain either a distance

    measurement or -1, if it is impossible to determine the

    appropriate distance.

Sample Input

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6 1
1 4 3
2 6 6

Sample Output

13
-1
10

Hint

At time 1, FJ knows the distance between 1 and 6 is 13.
At time 3, the distance between 1 and 4 is still unknown.
At the end, location 6 is 3 units west and 7 north of 2, so the distance is 10.

Source

USACO 2004 February

二、算法分析说明与代码编写指导

首先,只有互相连通的农场之间才能判定曼哈顿距离。因为给出的是农场之间的相对距离,当访问超出所在的连通块后,这个连通块内全部的相对距离就失效了,无法定位连通块外面的农场。
一般的并查集不能直接套用,这里介绍带权并查集。
毫无疑问,互相连通的农场要合并到一起。连通的农场之间的坐标差可以作为带权并查集的权(压缩路径以后,权就变成与根节点的坐标差。在本题中,这个权无论作为点权还是边权都不影响计算)。但是如何维护这个权呢?想到这个方法是有一定难度的。
首先就是合并的时候。设把 v 点所在的集合合并到 u,画图可知其根节点 fv 的权要改成:
w(fv) = p - w(v) + w(u),p 为 u 和 v 的坐标差。
这里简单说明一下,这份代码实现并查集的时候总是将 v 所在的集合合并为 u,但题目给的数据是 u 到 v 的坐标差 p。当 v 所在集合的深度更大时,为实现按秩合并,代码是将 v 和 u 以及找到的根 fv 和 fu 交换的,那么 p(x, y) 也要变成中心对称的坐标 (-x, -y)。我一直没有意识到这点,搞到样例都过不了,卡了很久,郁闷死了。
画图直接按合并前的状态画就可以,压缩路径以后不影响画出的图的正确性。
然后就是查找的过程中要压缩路径,这时候经过压缩路径的全部点的权都要改变。在找根的过程中把经过的点权都累加起来,直到找到根。在压缩路径的时候,把该点的权直接改成刚才累加的一直到根的权和(到根节点的总坐标差),然后从累加的权中扣除这个点到原来的父节点这一段贡献的权,然后对其父节点也做类似的操作,以此类推。
录入数据的时候,询问是乱序的,要先按照询问给出的时间排序,并查集的全部合并操作执行完毕后,要把询问恢复为原来给出的顺序再输出。
并查集部分的代码是基于如下的并查集模板修改的:

template<size_t n> class union_find {
private:
	unsigned root[n]; int rank[n];
public:
	union_find<n>() { init(); }
	union_find<n>(const bool& WannaInit) { if (WannaInit == true)init(); }
	void init() {
		fill(rank, rank + n, 1); for (unsigned i = 0; i < n; ++i)root[i] = i;
	}
	unsigned find_root(const unsigned& v) {
		unsigned r = v, t = v, u;
		if (t == root[v])return v;
		while (r != root[r]) { r = root[r]; }
		while (t != r) { u = root[t]; root[t] = r; t = u; }
		return r;
	}
	void path_compress() { for (unsigned i = 0; i < n; ++i)find_root(i); }
	void merge(unsigned u, unsigned v) {
		unsigned fu = find_root(u), fv = find_root(v); int d = rank[fu] - rank[fv];
		if (d < 0) { swap(fu, fv); swap(u, v); }
		else if (d == 0)++rank[fu];
		root[fv] = fu;
	}
	void merge_no_path_compression(const unsigned& u, const unsigned& v) { 
		root[v] = u; if (rank[u] == rank[v])++rank[u];
	}
	void merge_directly(const unsigned& u, const unsigned& v) { root[v] = u; }
	unsigned _rank(const unsigned& v) { return rank[find_root(v)]; }
	size_t size() { return n; }
};

三、AC 代码(110 ms)

#include<cstdio>
#include<algorithm>
#pragma warning(disable:4996)
using namespace std;
const unsigned nmax = 40001, kmax = 10000;
struct edge { unsigned f1, f2; int l; char d[2]; }; struct query { unsigned f1, f2, i, it; };
struct point {
	int x, y;
	point operator+(const point& r) { static point a; a.x = this->x + r.x; a.y = this->y + r.y; return a; }
	point operator-(const point& r) { static point a; a.x = this->x - r.x; a.y = this->y - r.y; return a; }
	const point& operator+=(const point& r) { this->x += r.x; this->y += r.y; return *this; }
	const point& operator-=(const point& r) { this->x -= r.x; this->y -= r.y; return *this; }
};
point p, w1, w2;
inline bool cmp(const query& q, const query& r) { return q.i < r.i; }
inline bool cmpt(const query& q, const query& r) { return q.it < r.it; }
template<size_t n> class union_find {
private:
	unsigned root[n]; int rank[n]; point weight[n];
public:
	union_find<n>() { init(); }
	union_find<n>(const bool& WannaInit) { if (WannaInit == true)init(); }
	void init() {
		fill(rank, rank + n, 1); for (unsigned i = 0; i < n; ++i)root[i] = i;
	}
	unsigned find_root(const unsigned& v) {
		unsigned r = v, t = v, u; point w = { 0,0 }, wT;
		if (t == root[v])return v;
		while (r != root[r]) { w += weight[r]; r = root[r]; }
		while (t != r) { u = root[t]; root[t] = r; wT = weight[t]; weight[t] = w; w -= wT; t = u; }
		return r;
	}
	void path_compress() { for (unsigned i = 0; i < n; ++i)find_root(i); }
	void merge(unsigned u, unsigned v, const point& w) {
		unsigned fu = find_root(u), fv = find_root(v); int d = rank[fu] - rank[fv];
		if (d < 0) { swap(fu, fv); swap(u, v); p.x *= -1; p.y *= -1; }
		else if (d == 0)++rank[fu];
		root[fv] = fu; weight[fv] = p - weight[v] + weight[u];
	}
	void merge_no_path_compression(const unsigned& u, const unsigned& v) {
		root[v] = u; if (rank[u] == rank[v])++rank[u];
	}
	void merge_directly(const unsigned& u, const unsigned& v) { root[v] = u; }
	unsigned _rank(const unsigned& v) { return rank[find_root(v)]; }
	size_t size() { return n; }
	point& _weight(const unsigned& v) { return weight[v]; }
};
unsigned n, m, k, c; int a[kmax]; union_find<nmax> u; edge e[nmax], E; query q[kmax], Q;
int main() {
	scanf("%u%u", &n, &m);
	for (unsigned i = 0; i < m; ++i) { scanf("%u%u%d%s", &e[i].f1, &e[i].f2, &e[i].l, e[i].d); }
	scanf("%u", &k);
	for (unsigned i = 0; i < k; ++i) { scanf("%u%u%u", &q[i].f1, &q[i].f2, &q[i].i); q[i].it = i; }
	sort(q, q + k, cmp); unsigned I;
	for (unsigned i = 0; i < k; ++i) {
		copy(q + i, q + i + 1, &Q); I = Q.i;
		for (; c < I; ++c) {
			copy(e + c, e + c + 1, &E);
;			switch (E.d[0]) {
			case 'N':p.x = 0; p.y = E.l; u.merge(E.f1, E.f2, p); break;
			case 'E':p.x = E.l; p.y = 0; u.merge(E.f1, E.f2, p); break;
			case 'W':p.x = -E.l; p.y = 0;; u.merge(E.f1, E.f2, p); break;
			default:p.x = 0; p.y = -E.l; u.merge(E.f1, E.f2, p);
			}
		}
		if (u.find_root(Q.f1) != u.find_root(Q.f2)) { a[i] = -1; }
		else { w1 = u._weight(Q.f1); w2 = u._weight(Q.f2); a[i] = abs(w2.x - w1.x) + abs(w2.y - w1.y); }
	}
	sort(q, q + k, cmpt);
	for (unsigned i = 0; i < k; ++i)printf("%d\n", a[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值