距离咨询[tarjan求lca]

农夫约翰有N(2<=N<=40000)个农场,标号1到N。M(2<=M<=40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样,图中农场用F1..F7表示:

每个农场最多能在东西南北四个方向连结4个不同的农场。此外,农场只处在道路的两端。道路不会交叉而且每对农场间有且仅有一条路径。邻居鲍伯要约翰来导航,但约翰丢了农场的地图,他只得从电脑的备份中修复率。每一条道路的信息如下:

从农场23往南经距离10到达农场17

从农场1往东经距离7到达农场17

. . .

最近美国过度肥胖非常普遍。农夫约翰为了让他的奶牛多做运动,举办了奶牛马拉松。马拉松路线要尽量长。

奶牛们拒绝跑马拉松,因为她们悠闲的生活无法承受约翰选择的如此长的赛道。因此约翰决心找一条更合理的赛道。他打算咨询你。读入地图之后会有K个问题,每个问题包括2个整数,就是约翰感兴趣的2个农场的编号,请尽快算出这2个农场间的距离。

输入格式

第1行:两个分开的整数N和M。

第2到M+1行:每行包括4个分开的内容,F1,F2,L,D分别描述两个农场的编号,道路的长度,F1到F2的方向N,E,S,W。

第2+M行:一个整数K(1<=K<=10000).

第3+M到2+M+K行:每行输入2个整数,代表2个农场。

输出格式

对每个问题,输出单独的一个整数,给出正确的距离。

样例

样例输入

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6

样例输出

13
3
36

思路

注意到每两个农场间只有一条路相连. 那就是一棵树, 至于方向, 完全没有意义,在读入时直接忽略即可

两个节点间的最短路, 就可以用lca求, 这里我们用tarjan求lca:

我们需要两个边表, 一个存储原图, 一个存储询问.

看注释吧

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 80005;
int n, m, head[maxn], len=-1, dfn[maxn], tot=-1, headq[maxn], vis[maxn], dis[maxn], f[maxn];
struct edge{
	int to, nx, w;
}e[maxn], q[maxn];
void add(int x, int y, int w){//建图
	e[++len].to = y;
	e[len].nx = head[x];
	e[len].w = w;
	head[x] = len;
	e[++len].to = x;
	e[len].nx = head[y];
	e[len].w = w;
	head[y] = len; 
}
void add_que(int u, int v){//存储询问
	q[++tot].to = v;
	q[tot].nx = headq[u];
	headq[u] = tot;
	q[++tot].to = u;
	q[tot].nx = headq[v];
	headq[v] = tot;
}
void init(int n){//初始化
	for(int i=0; i<=n; i++) f[i] = i;//并查集初始化
	memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    memset(head,-1,sizeof(head));  
    memset(headq,-1,sizeof(headq));
}
int find(int x){//并查集求祖先
	if(x != f[x]) f[x] = find(f[x]);
	return f[x];
}
void tarjan(int root){
	vis[root] = 1;//标记访问
	f[root] = root;//初始化父亲
	for(int i=head[root]; ~i; i=e[i].nx){//遍历子节点
		int v = e[i].to;//子节点
		if(!vis[v]){//如果没访问过
			dis[v] =  dis[root] + e[i].w;//子节点到根节点距离=父节点到根节点距离+边权
			tarjan(v);//递归子节点
			f[v] = root;//更新父亲
		}
	}
	for(int i=headq[root]; ~i; i=q[i].nx){//处理以当前根节点为起点的询问
		int v = q[i].to;//终点
		if(vis[v]){//如果已经计算过了
			q[i].w = dis[root] + dis[v] - 2*dis[find(v)];//这次询问的距离=起点到祖先的距离+终点到祖先的距离-2*起点终点lca到祖先的距离
			q[i^1].w = q[i].w;
		}
	}
}
int main(){
	scanf("%d%d", &n, &m);
	init(n);
	for(int i=1; i<=m; i++){
		int f1, f2, l; scanf("%d%d%d%*c%*c", &f1, &f2, &l);//方向是无用的,直接忽略
		add(f1, f2, l);
	}
	int k; scanf("%d", &k);
	for(int i=1; i<=k; i++){
		int u, v; scanf("%d%d", &u, &v);
		add_que(u, v);
	}
	tarjan(1);
	for(int i=0; i<tot; i+=2) printf("%d
", q[i].w);
	return 0;
}

在这里我解释一下为什么find(v)就可以求出lca
看这个图.

假如我们求4 , 7 间的最短路.

在我们tarjan的时候, 只有根节点的子树都tarjan完时,才会更新他并查集父节点.当我们tarjan(7)时,发现有和7有关的询问,并且4已经访问过, 这时f[3]仍为3, find(3) 就是3, 即lca, 假如4还有子树的话, tarjan(7)时4这边的子树已经访问完, fa[3]已经更新, 所以, 我们可以这样求出lca

原文地址:https://www.cnblogs.com/hzoi-poozhai/p/12815059.html