2017百度之星初赛(B) 1002 Factory(倍增法求LCA)

题目链接:点击打开链接

思路:

树形图上的最短距离,很容易想到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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值