LCA(最近公共祖先 Tarjan) CodeVs-2370-小机房的树

本文介绍了一种高效算法,用于解决树状结构中两个节点间的最短距离问题,特别适用于节点数量巨大和频繁查询场景。通过构建树的邻接列表表示,并采用并查集寻找最近公共祖先,实现快速计算。

传送门

2370 小机房的树
 时间限制: 1 s
 空间限制: 256000 KB
 题目等级 : 钻石 Diamond
 题解
 查看运行结果
题目描述 Description
小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力

输入描述 Input Description
第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。
第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点
输出描述 Output Description
一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。

样例输入 Sample Input
3

1 0 1

2 0 1

3

1 0

2 0

1 2

样例输出 Sample Output
1


1


2
数据范围及提示 Data Size & Hint
1<=n<=50000, 1<=m<=75000, 0<=c<=1000


题意:就像上面所说的,一棵树,然后每条边有权值,然后给定两个点,要求找一个点,让其他两个点到那里相聚,目的点可以是给定点的其中一个。然后要你求出最小的距离。

其实我们知道,对于一棵树来说,两个点之间不走回路的路径是唯一的,而且也是最短的。也就是说,选定哪个点做目的点根本不重要,因为在这两个点之间只有唯一的路径,而在这条路径上的目的点造成的路径距离都是一样的。所以只要是这条路径上的点都行。那么问题就与目的点无关了,问题就只变成求树上两个点之间的距离了。

点有 50000 询问有 75000 。

要求两个点之间的距离,得先找到两个点之间的最近公共祖先点 z

做一个距离前缀和,记录的是根节点到当前点的距离(路径唯一)记为 sum[]

那么 两点之间距离 = sum[x] + sum[y] - 2 * sum[z]

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
#define ll long long
#define maxn 50005
#define maxm 75005
#define mem(a,x) memset(a,x,sizeof(a))

struct node {
	int v,c;
};
struct ques {
	int v,id;
};
vector<node>e[maxn];  // 记录树边 
vector<ques>q[maxn];  // 记录与每个点有联系的询问,并记录询问顺序 

int ans[maxm],fa[maxn],vis[maxn],sum[maxn],n,m;

int find(int x) {   // 并查集 
	while(x != fa[x]) {
		x = fa[x];
	}
	return x;
}

void dfs(int u,int pre,int val) {
	sum[u] = val;
	int sz = e[u].size();
	for(int i = 0; i < sz; i++) {
		int v = e[u][i].v;
		int c = e[u][i].c;
		if(v == pre) {
			continue;
		} else {
			dfs(v,u,val + c);
			fa[v] = u;
		}
	}
	int qsz = q[u].size();
	for(int i = 0; i < qsz; i++) {
		int v = q[u][i].v;
		if(vis[v]) {
			int id = q[u][i].id;
			int aim = find(v);
			ans[id] = sum[u] + sum[v] - 2 * sum[aim];
		}
	}
	vis[u] = 1;
}

void init() {
	mem(vis,0);
	for(int i = 1; i <= n; i++) {
		fa[i] = i;
	}
}
int main() {
	int u,v,c;
	scanf("%d",&n);
	init();
	for(int i = 1; i < n; i++) {
		scanf("%d %d %d",&u,&v,&c);
		node tmp;
		tmp.v = v,tmp.c = c;
		e[u].push_back(tmp);
		tmp.v = u;
		e[v].push_back(tmp);
	}
	scanf("%d",&m);
	for(int i = 1; i <= m; i++) {
		scanf("%d %d",&u,&v);
		ques tmp;
		tmp.v = v,tmp.id = i;
		q[u].push_back(tmp);
		tmp.v = u;
		q[v].push_back(tmp);
	}
	dfs(0,-1,0);
	for(int i = 1; i <= m; i++) {
		printf("%d\n",ans[i]);
	}
	return 0;
}

/*
11
0 1 3
2 0 6
3 1 2
1 4 4
4 9 7
4 10 9
2 5 5
5 6 5
7 5 3
8 7 2
5
3 2
5 1
7 4
9 6
8 10
*/


最近公共祖先(Lowest Common Ancestor, LCA)问题是指在给定一棵中找到两个节点的最短路径上的共同祖先Tarjan算法通常用于解决这个问题,但它的主要目的是为了发现图中的强连通分量(Strongly Connected Components, SCC),而不是直接计算LCA。不过,由于这两种问题都涉及到深度优先搜索和拓扑排序的思想,所以我们可以借助Tarjan算法的思路来理解LCATarjan的算法是基于深度优先搜索和一种称为“DFS”的数据结构。在寻找LCA的过程中,如果能找到两个节点在同一棵DFS或它们的DFS祖先相同,那么这两个节点就是最近公共祖先。这里的关键在于维护节点的前驱(pred)和后继(succ)指针,以及一个秩(rank)数组来判断边的方向,以确定节点是否构成一个回路。 下面是 Tarjan 算法的主要步骤: 1. 初始化:对于每个未访问的节点 u,设置其秩 rank[u] = 次序号(u 的编号),低link[u] = u(表示 u 的父节点),深度 depth[u] = 0,访问次数和栈顶指针为 null。 2. DFS 递归过程:从根节点开始遍历,对每个子节点 v,执行以下操作: a. 如果 v 没有被访问过,则进行一次深度优先搜索,更新深度、秩和低link信息。 b. 记录 v 的秩 rank[v] 和当前的深度 depth[v],并将 v 加入到相应的 DFS 。 c. 更新 v 的所有前驱和后继指针。 d. 如果 v 是回路的一部分,将 v 设置为它自身的低link,这会导致算法在下一个阶段检测到回路。 3. 完成搜索后,对每个节点 u,检查 lowlink[u] 是否等于 u,如果是,说明 u 和 u 是同一个强连通分量内的节点。同时,这也帮助我们找到 u 和其他节点的最近公共祖先,因为他们在同一棵上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值