HDU 2586 How far away?(LCA使用详解)

关键词:LCA、并查集、动态规划、深度优先搜索、哈希、RMQ、递归

题目:

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

这个题目只有建立一个树,然后查询任意2个点之间的距离,没有更新操作,所以可以用LCA来做。

LCA就是寻找最近公共祖先,这有什么用呢?

这是因为有一个性质,假设B和C的最近公共祖先是A,那么对于整个树的根节点D,

都有:|BD|+|CD|-|AD|*2=|BC|

也就是说,只要事先求出所有点到D的距离dist(dist的大小为n),

然后对于输入的B和C,只需要求出最近公共祖先,即可利用上式得到答案。

1,建树

建树的方法有很多,而且n个点都是可以用来当做根节点的。

为了方便,边输入边建树,我把输入的x,y,l理解为x为父亲,y为儿子,l当然是距离。

因为在计算dist之前需要找到根节点,所以需要1个数组fa记录父亲的标号(fa的大小为n

因为儿子的数量未知,但是总数是n-1,所以用向量数组v来存比较合适。(v的大小为n

2,寻找根节点

这里用并查集的方法,fa数组初始化为指向自身,即fa[i]=i

然后在输入n-1条边的时候,除了建树还要调整fa的值,1条边就调整1个点的fa值,

最后只有根节点还满足fa[i]=i,其他的点的fa值都已经指向父亲,

此时任选一点(比如最后输入的那个x),用while循环便可找到根节点

3,深度优先搜索

既然找到了根节点,那么就要开始深度优先搜索了。

利用深度优先,把树映射到一维数组,这是哈希的一种。

得到这个表有什么用呢?

也不知道是哪个神人想出来的,仅用这个表就可以得到最近公共祖先。

比如B的遍历数为3,C的遍历数为6,从3到6,深度依次为3 2 3 4,

最小的是2,对应的是A,所以A就是B和C的最近公共祖先。

注意,遍历数不一定是唯一的,比如求A和I的最近公共祖先,A有3个不同的遍历数,

但是不管选哪个,结果都是一样的。

所以说,只需要用动态规划的RMQ方法求出区间的最小值,即可求出公共祖先。

4,空间分析

首先,我们需要对每个点存1个遍历数,任选1个存起来即可。(visitn的大小为n

然后,我们还需要把所有的遍历数对应的是哪个点存起来。

那么,一共有多少个遍历数呢?

规律很明显,总结如下:1个叶子节点只有1个遍历数,每个节点的遍历数等于出度加1

所以遍历数一共有:节点总数+出度总数=n+n-1(visitnum的大小为n+n-1

mins第一个维度的大小为n+n-1,第二个维度约为log2(n)+1

上面的9个节点就有17个遍历数。

5,计算每个点到根节点的距离。

visit函数是一个递归调用的函数,用来实现深度优先搜索。

搜索的过程中,除了要计算visitn和visitnum,还要计算deep和dist(deep的大小为n

(至此,7个数组的用途和大小都用蓝色粗体标注了)

deep和dist都可以利用递归的参数d和dis非常方便的计算出来。

6,RMQ

用RMQ求的是什么,这个倒是不要搞错了。

RMQ求的是一段区间中,拥有最小deep的那个点对应的visitnum。

而不是求deep的最小值,更不是求visitnum的最小值。

7,LCA

这个和RMQ对应,求的是一段区间中,拥有最小deep的那个点对应的visitnum。

只需要根据visitnum便可知道到底哪个点是最近公共祖先

8,查询

输入x,y,取出他们的遍历数visitn,由此求出他们的最近公共祖先。

需要注意的是,因为遍历数有很多个,随便取一个存到visitn数组中,

那么x和y的遍历数谁大谁小就完全不知道了,需要判断。

代码:

#include<iostream>
#include<vector>
using namespace std;

struct node
{
	int son;
	int distance;
};

int n;
vector<node>v[40001];//存儿子标号
int deep[40001];//每个点的深度
int visitnum[80001];//遍历数是2*n-1
int visitn[40001];//每个点的任意一个遍历数
int vnum = 1;
int mins[80001][18];		//区间最小值
int dist[40001];		//每个点到祖先的距离distance
int fa[40001];

void visit(int m, int d,int dis)		//遍历重编号、计算distance
{
	vector<node>::iterator p;
	deep[m] = d;
	dist[m] = dis;
	for (p = v[m].begin(); p != v[m].end(); p++)
	{
		visitn[m] = vnum;
		visitnum[vnum++] = m;	//存入访问的第vnum个点是哪个点
		visit((*p).son, d + 1, dis + (*p).distance);
	}
	visitn[m] = vnum;		//注意这2句的顺序
	visitnum[vnum++] = m;
}

void rmq()		//计算区间最小值
{
	for (int i = 1; i <= 2 * n - 1; i++)mins[i][0] = visitnum[i];
	for (int j = 1; (1 << j) <= 2 * n - 1; j++)
	{
		for (int i = 1; i <= 2 * n - 1; i++)
		{
			mins[i][j] = mins[i][j - 1];
			int k = i + (1 << (j - 1));
			if (k <= 2 * n - 1 && deep[mins[i][j]] > deep[mins[k][j - 1]])
			mins[i][j] = mins[k][j - 1];
		}
	}
}

int lca(int x,int y)	//求最近公共祖先
{
	int j = 0;
	while ((1 << j) <= y - x)j++;
	j--;
	int min = mins[y + 1 - (1 << j)][j];
	if (deep[min] > deep[mins[x][j]])min = mins[x][j];
	return min;
}

int main()
{
	int t, m, x, y, l;
	node nod;
	cin >> t;
	while (t--)
	{
		cin >> n >> m;
		for (int i = 1; i <= n; i++)
		{
			v[i].clear();	//初始化
			fa[i] = i;
		}
		for (int i = 1; i < n; i++)
		{
			scanf("%d%d%d", &x, &y, &l);
			nod.distance = l;
			nod.son = y;
			v[x].insert(v[x].end(), nod);		//存入儿子的标号和距离
			fa[y] = x;	//存入父亲的标号
		}
		while (fa[x] != x)x = fa[x];	//寻找整个树的根节点
		visit(x, 1, 0);
		rmq();
		while (m--)
		{
			scanf("%d%d", &x, &y);
			int lca_;
			if (visitn[x] <= visitn[y])lca_ = lca(visitn[x], visitn[y]);
			else lca_ = lca(visitn[y], visitn[x]);//注意判断
			printf("%d\n", dist[x] + dist[y] - dist[lca_] * 2);
		}
	}
	return 0;
}
时间: 2024-08-02 23:53:15

HDU 2586 How far away?(LCA使用详解)的相关文章

HDU 2586 How far away LCA的离线算法 Tarjan

链接: How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 11204    Accepted Submission(s): 4079 Problem Description There are n houses in the village and some bidirectional roads connec

HDU 4049 Tourism Planning (状压dp 详解)

Tourism Planning Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1125    Accepted Submission(s): 487 Problem Description Several friends are planning to take tourism during the next holiday. Th

LCA(最近公共祖先)--tarjan离线算法 hdu 2586

HDU 2586 How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 11320    Accepted Submission(s): 4119 Problem Description There are n houses in the village and some bidirectional roads c

HDU 2586 How far away ? (LCA最近公共祖先)

题目地址:HDU 2586 LCA第一发. 纯模板题. 偷懒用的vector,结果一直爆栈.把G++改成C++就过了.. 代码如下: #include <iostream> #include <string.h> #include <math.h> #include <queue> #include <algorithm> #include <stdlib.h> #include <map> #include <se

hdu 2586 LCA模板题(离线算法)

http://acm.hdu.edu.cn/showproblem.php?pid=2586 Problem 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&quo

最近公共祖先(lca) hdu 2586

hdu 2586 How far away ? 题目大意:给定n-1条边构成一棵树,无向的:和m个询问,对于每一个询问按顺序回答. 结题思路:lca算法算出最近公共祖先,然后dis[u]+dis[v]-2*dis[father](father是u,v的最近公共祖先),小trick是在构造询问树的时候把权值设成询问对应的输入顺序 #include <iostream> #include <cstdio> #include <cstring> #include <al

hdu 2586 How far away ?倍增LCA

hdu 2586 How far away ?倍增LCA 题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2586 思路: 针对询问次数多的时候,采取倍增求取LCA,同时跟新距离数组 因为 \(2^{16} > 40000\) 所以所以表示祖先的数组dp[][]第二维取到16即可 就这道题来说,与比较tarjan比较,稍快一点 代码: #include <iostream> #include <algorithm> #includ

HDU - 2586 - How far away ?

先上题目: How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4936    Accepted Submission(s): 1866 Problem Description There are n houses in the village and some bidirectional roads conne

HDU 2586 How far away?

http://acm.hdu.edu.cn/showproblem.php?pid=2586 题意:给定一个图,有M个询问.每一次询问为询问u,v两个点的距离为多少? 题解:LCA问题. 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <cstdlib> 5 #include <cmath> 6 #include <string> 7