题目链接:点击打开链接
思路:
树形图上的最短距离,很容易想到LCA算法,树形图上的两个节点的最短距离是唯一的、确定的,是两个节点到其最近公共祖先的距离之和。直接枚举两个分公司的办公室,两两算出最短路径,取最小值即为结果。
具体利用LCA求出最短距离的方法是,先预处理,深搜一遍树形图,得到每个节点距离树根的距离,则最短距离为dist[x] + dist[y] - 2 * dist[ lca(x,y)]。
此处LCA的具体实现采用倍增法,还可以采用dfs + ST表,都为在线算法,实时查询。感觉此题用Tarjan离线算法不合适,LCA算法进行前要存储所有查询,此题事先存储要查询的点对不光麻烦,更可怕的是,存储量最坏会达到10^10级别,会超内存。
// Factory.cpp 运行/限制:858ms/10000ms(对,没看错,就是10000......)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
#define INF 0X3f3f3f3f
int n;
int cnt, head[100005];
int depth[100005], dist[100005];//深度,距离根节点距离
int parent[20][100005];//parent[i][v]表示v节点往上走2^i步到达的顶点
vector<int> office[100005];//每个分公司的办公室所在城市
struct node {
int to, next, weight;
}edge[200010];
void add(int x, int y, int w) {//树加边
cnt++;
edge[cnt].to = y;
edge[cnt].weight = w;
edge[cnt].next = head[x];
head[x] = cnt;
}
void dfs(int x, int fa) {//搜索得每个点的深度,与根节点的距离
parent[0][x] = fa;
for (int i = head[x]; i; i = edge[i].next) {
int y = edge[i].to;
if (y == fa) continue;
depth[y] = depth[x] + 1;
dist[y] = dist[x] + edge[i].weight;
dfs(y, x);
}
}
void init() {//预处理,打2^k表
memset(depth, 0, sizeof(depth));
memset(dist, 0, sizeof(dist));
memset(parent, 0, sizeof(parent));
dfs(1, 0);
for (int i = 0; i + 1 < 20; i++) {
for (int j = 1; j <= n; j++) {
if (!parent[i][j]) parent[i + 1][j] = 0;
else parent[i + 1][j] = parent[i][parent[i][j]];
}
}
}
int lca(int x, int y) {//LCA
if (depth[x] > depth[y]) swap(x, y);
for (int i = 0; i < 20; i++) {
if ((depth[y] - depth[x]) >> i & 1) {
y = parent[i][y];
}
}
if (x == y) return x;
for (int i = 19; i >= 0; i--) {
if (parent[i][x] != parent[i][y]) {
x = parent[i][x];
y = parent[i][y];
}
}
return parent[0][x];
}
int main(){
int t, m, q;
int x, y, w;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
cnt = 0;
memset(head, 0, sizeof(head));
for (int i = 1; i < n; i++) {
scanf("%d%d%d", &x, &y, &w);
add(x, y, w);
add(y, x, w);
}
for (int i = 1; i <= m; i++) {
int count, num;
scanf("%d", &count);
for (int j = 0; j < count; j++) {
scanf("%d", &num);
office[i].push_back(num);
}
}
init();
scanf("%d", &q);
for (int i = 0; i < q; i++) {
int re = INF;
scanf("%d%d", &x, &y);
//枚举x,y的所有办公室,两两求距离
for (int j = 0; j < office[x].size(); j++) {
for (int t = 0; t < office[y].size(); t++) {
int a = office[x][j];
int b = office[y][t];
re = min(re, dist[a] + dist[b] - 2 * dist[lca(a,b)]);
}
}
printf("%d\n", re);
}
for (int i = 1; i < 100005; i++) {
office[i].clear();
}
}
return 0;
}