HDU-2586 How far away ? LCA-Tarjan Or spfa
题意:给定N个节点一棵树,现在要求询问任意两点之间的简单路径的距离,其实也就是最短路径距离。
解法:spfa:直接对每一对点作一次spfa即可。Tarjan:求出两个点A,B之间的最近公共祖先C,设根到每个点的最短距离为dis[],那么距离就是dis[A]+dis[B]-2*dis[C]。而根到各个点的距离一次dfs就出来了,因此问题转化为求出两点之间的最近公共祖先,Tarjan算法能够离线解决所询问的点对。原理如下:
对于一次dfs,当第一次遍历到点u时,那么令set[u] = u。遍历完以u为根的树后,将u所属集合指向双亲节点。接下来查看u的访问列表中有没有询问<u, v>的点对,如果有且v已经被访问过,则<u, v>的最近公共祖先为find(v)。如果u是v下面的节点,那么根据这种遍历方式,显然find(v) = v,否则find(v)指向的最近的一个公共祖先,因为u的进入一定是经过这个最近的公共祖先进入的。
代码如下:Spfa:62MS Tarjan 15MS
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <iostream> using namespace std; int N, Q; struct Edge { int v, fee, next; }; Edge e[80005]; struct Node { int v, num, next; }q[405]; int idx, head[40005]; int cnt, link[40005]; int set[40005]; int dis[40005]; int rec[205][3]; char vis[40005]; void insert(int a, int b, int fee) { e[idx].v = b, e[idx].fee =fee; e[idx].next = head[a]; head[a] = idx++; } void push(int a, int b, int num) { q[cnt].v = b, q[cnt].next = link[a]; q[cnt].num = num, link[a] = cnt++; } int find(int x) { return set[x] = x == set[x] ? x : find(set[x]); } void tarjan(int u) { vis[u] = 1; // 标记该点已经走过 set[u] = u; // 在第一次进入该点的时候就将该点标记为暂时的根节点,为孩子节点指向它做准备 for (int i = link[u]; ~i; i = q[i].next) { // 查看该节点后面时候有询问存在 int v = q[i].v; if (vis[v]) { rec[q[i].num][2] = find(v); } } for (int i = head[u]; ~i; i = e[i].next) { int v = e[i].v; if (!vis[v]) { dis[v] = dis[u] + e[i].fee; tarjan(v); set[v] = u; // 此时放弃孩子节点为根,调整孩子节点的根指向更上层的节点 } } } int main() { int T, a, b, fee; for (scanf("%d", &T); T; T--) { scanf("%d %d", &N, &Q); cnt = idx = 0; memset(head, 0xff, sizeof (head)); memset(link, 0xff, sizeof (link)); for (int i = 1; i < N; ++i) { scanf("%d %d %d", &a, &b, &fee); insert(a, b, fee), insert(b, a, fee); } for (int i = 0; i < Q; ++i) { scanf("%d %d", &a, &b); push(a, b, i), push(b, a, i); // Tarjan是一个离线算法,因此需要先保存询问 rec[i][0] = a, rec[i][1] = b; // 记录起询问 } memset(vis, 0, sizeof (vis)); // 标记点 dis[1] = 0; tarjan(1); for (int i = 0; i < Q; ++i) { printf("%d\n", dis[rec[i][0]]+dis[rec[i][1]]-2*dis[rec[i][2]]); } } return 0; }
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int N, Q; struct Edge { int v, fee, next; }e[80005]; int idx, head[40005]; int dis[40005]; void insert(int a, int b, int fee) { e[idx].v = b, e[idx].fee = fee; e[idx].next = head[a]; head[a] = idx++; } int que[400005]; int front, tail; char vis[40005]; int spfa(int s, int t) { memset(dis, 0x3f, sizeof (dis)); memset(vis, 0, sizeof (vis)); front = tail = 0; que[tail++] = s; dis[s] = 0; while (front < tail) { int v, u = que[front++]; vis[u] = 0; for (int i = head[u]; ~i; i = e[i].next) { v = e[i].v; if (dis[v] > dis[u] + e[i].fee) { dis[v] = dis[u] + e[i].fee; if (!vis[v]) { vis[v] = 1; que[tail++] = v; } } } } return dis[t]; } int main() { int T, a, b, fee; scanf("%d", &T); while (T--) { scanf("%d %d", &N, &Q); idx = 0; memset(head, 0xff, sizeof (head)); for (int i = 1; i < N; ++i) { scanf("%d %d %d", &a, &b, &fee); insert(a, b, fee), insert(b, a, fee); } for (int i = 0; i < Q; ++i) { scanf("%d %d", &a, &b); printf("%d\n", spfa(a, b)); } } return 0; }