AcWing 1171. 距离(tarjan算法求LCA)

Description

给出 n 个点的一棵树,多次询问两点之间的最短距离。

注意: 边是无向的。

所有节点的编号是 1, 2, …, n 。

Input

第一行为两个整数 n , m  ( 2 ≤ n ≤ 104 , 1 ≤ m ≤ 2×104 )。n 表示点数,m 表示询问次数;

下来 n - 1 行,每行三个整数 x, yk  ( 1 ≤ xy ≤ n  ,  0 < k ≤ 100  ),表示点 x 和点 y 之间存在一条边长度为 k

再接下来 m 行,每行两个整数 x, y,表示询问点 x 到点 y 的最短距离。

树中节点编号从 1 到 n 。

Output

共 m 行,对于每次询问,输出一行询问结果

Sample Input

sample #1
2 2
1 2 100
1 2
2 1

sample #2
3 2
1 2 10
3 1 15
1 2
3 2

Sample Output

sample #1
100
100

sample #2
10
25

tarjan求LCA:
属于离线做法:需要先记录每个询问,在做完tarjan算法后将每个询问输出

在深搜时,利用st[]代表各结点的状态,
st[i] = 1表示该节点在当前搜索的分支中;
st[i] = 2表示该节点的子树全部被搜索过;
st[i] = 0表示该节点未被搜索过。
并查集的使用:在每次回溯时添加回溯节点为父节点,在回溯到当前节点时,寻找包含当前节点的询问,若询问中的另一节点状态st[i]=2,则LCA为另一节点的祖宗节点;否则跳过该询问。


下面通过举例,模拟tarjan算法过程


假设题目要求查询8和5,4和5,2和4的LCA
利用深搜,首先从根节点1向下遍历,st[1] = 1;
继而遍历到节点2,st[2] = 1;
继而遍历到节点4,st[4] = 1;
继而遍历到8,st[8] = 1;此时发现8没有子树,于是令st[8]=2;访问另一节点5,因为st[5]!=2,所以跳过该询问。
继而回溯到4,令per[8] = 4;st[4] = 2;此时访问另一节点5,因为st[5]!=2,所以跳过该询问。访问另一节点2,因为st[2]!=2,因此跳过该询问。
继而回溯到2,令per[4] = 2;st[2] = 2;此时访问另一节点4,因为st[4]=2,所以2和4的LCA为find(4)=2;
继而访问5,令st[5] = 1;此时发现5没有子树,于是令st[5]=2;访问另一节点8,因为st[8] = 2;因此5和8的LCA为find(8) = 2;访问另一节点4,因为st[4] = 2;因此5和4的LCA为find(4)  = 2;


代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;

#define N 20010
typedef pair<int, int> pii;
int n, m;
vector<pii>qurey[N];//其中sec存放查询编号,fir存放另一个查询节点
int p[N];
int dist[N], st[N], ans[N];
int h[N], e[N*2], w[N*2], ne[N*2], idx;
void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int find(int x) {
	if (x != p[x])p[x] = find(p[x]);
	return p[x];
}

void dfs(int x, int fa) {
	for (int i = h[x]; i != -1; i = ne[i]) {
		int j = e[i];
		if (j == fa)continue;
		dist[j] = dist[x] + w[i];
		dfs(j, x);
	}		 
}

void tarjan(int u) {
	st[u] = 1;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!st[j]) {
			tarjan(j);
			p[j] = u;
		}
	}

	
	for (pii t : qurey[u]) {
		int ver = t.first, id = t.second;
		if (st[ver] == 2) {
			int lca = find(ver);
			ans[id] = dist[ver] + dist[u] - 2 * dist[lca];
		}
	}st[u] = 2;
}

int a, b, c;
int main() {
    memset(h,-1,sizeof h);
	cin >> n >> m;
	for (int i = 0; i < n - 1; i++) {
		cin >> a >> b >> c;
		add(a, b, c), add(b, a, c);
	}
	for (int i = 0; i < m; i++) {
		cin >> a >> b;
		if (a != b) {//存储询问节点,当a==b时不处理,ans[i]全局变量为0
			qurey[a].push_back({ b,i });
			qurey[b].push_back({ a,i });
		}
	}

	for (int i = 1; i <= n; i++)p[i] = i;
	dfs(1, -1);
	tarjan(1);
	
	for (int i = 0; i < m; i++)cout << ans[i] << endl;

	return 0;
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值