HDU 2586——How far away ?

Time limit 1000 ms
Memory limit 32768 kB

Description

There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.

Input

First line is a single integer T(T<=10), indicating the number of test cases.
  For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
  Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.

Output

For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.

Sample Input

2
3 2
1 2 10
3 1 15
1 2
2 3

2 2
1 2 100
1 2
2 1

Sample Output

10
25
100
100

题目分析

       乍一看,这个是求最短路的问题,本想着prim算法弄完,但是奈何这个题目被归在了RMQ&LCA专题里面,而且这个结点的个数最大有4w个,为了防止用prim算法出现超时,最后还是老老实实的去学了LCA算法,我推荐一个学这个东西的地方,讲的还不错,有这个题目的原题讲解 https://www.bilibili.com/video/av41067872/?p=5 

       好了,来分析一下这个题目吧,求指定村庄之间的最短距离,因为要用到 Lowest Common Ancestor 算法,我们用dis[x]表示结点(也就是城市x)离根结点的距离,由于题目的特殊性, "But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses" , 这个地方说明了每两个城市之间的路是固定的,有且只有一条(想到这里,发现哪里是什么最短路,就是要你求两点之间的距离)。

     假设u,v为要求距离的两个城市,k表示二者的LCA,那么dis[u] - dis[k] 为u距离LCA的距离,而dis[v] - dis[k] 为v距离LCA的距离,所以dis[u] + dis[v] - 2 * dis[k] 就是两个城市之间的距离,然后其他的步骤就按照LCA算法求出LCA就好,LCA的教程请参考上面的哪个网址,个人觉得讲的很好。

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include <vector>
using namespace std;
const int inf = 0x3f3f3f3f;
const int max2 = 100 + 10;
const int max3 = 1000 + 10;
const int max4 = 10000 + 10;
typedef struct Edge{
	int to;
	int val;
	Edge(int to = 0 , int val = 0):to(to),val(val){}
}Edge;

vector<Edge>road[4 * max4];		//road[x]保存从x出发的所有道路,road[x][i].to表示这条路的另一个点
int dis[4 * max4];			//dis[x]记录结点x离根结点的距离
int father[4 * max4][15];		//father[x][j]记录从x点向上2^j次层的祖先结点
int depth[4 * max4];			//depth[x]记录点x的深度
int lg[4 * max4];			//log2n 向下取整

void init()
{
	memset(dis, 0, sizeof(dis));
	memset(father, 0, sizeof(father));
	memset(depth, 0, sizeof(depth));
	for (int i = 0; i < 4 * max4;i++)
		road[i].clear();
}

void dfs(int now,int fa)
{
	depth[now] = depth[fa] + 1;
	father[now][0] = fa;

	//将now点的祖先结点全部记录下来
	for (int j = 1; j <= lg[depth[now]] + 1; j++)
		father[now][j] = father[father[now][j - 1]][j - 1];

	//搜索当前结点的子结点
	for (int i = 0; i < road[now].size();i++)
	{
		if(road[now][i].to != fa)			               //如果某一条路是当前结点和父结点的,那么没有必要向上搜索
		{
			dis[road[now][i].to] = dis[now] + road[now][i].val;    //子结点到根结点的距离等于父结点到根结点的距离加上这条路的距离
			dfs(road[now][i].to, now);
		}
	}
}

//返回结点u,v的最小公共祖先
int lca(int u,int v)
{
	if (depth[u] < depth[v])
		swap(u, v);

	while (depth[u] != depth[v])
		u = father[u][lg[depth[u] - depth[v]]];

	if (u == v)
		return u;

	for (int i = lg[depth[u]];i >= 0;i--)
	{
		//当两者的某祖先结点不一致的时候,说明还需要继续向上查找
		//当u,v位lca的最近子结点的时候,对于任意的father[u][i]都满足
		//father[u][i] == father[v][i] ,所以最后u,v为lca的最近子结点
		if(father[u][i] != father[v][i])
		{
			u = father[u][i];
			v = father[v][i];
		}
	}
	return father[u][0];
}

int main()
{
	//预处理,lg[x]表示log2n向下取整
	lg[0] = -1;
	for (int i = 1; i < 4 * max4;i++)
		lg[i] = lg[i >> 1] + 1;

	int t;
	scanf("%d",&t);
	while(t--)
	{
		init();
		int n, m;
		scanf("%d%d", &n, &m);
		for (int i = 1; i < n; i++)
		{
			int s, e, v;
			scanf("%d%d%d", &s, &e, &v);
			road[s].push_back({ e,v });
			road[e].push_back({ s,v });
		}
		dfs(1, 0);					//树中任意一个结点均可以作为根结点,但是不确定结点的个数,而且编号为1的结点所以的树均有,所以将1作为根结点
		while(m--)
		{
			int s, e;
			scanf("%d%d", &s, &e);
			int k = lca(s, e);
			printf("%d
", dis[s] + dis[e] - 2 * dis[k]);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/winter-bamboo/p/10634449.html