【蓝桥杯】历届试题 大臣的旅费(2次深度优先搜索(dfs)、树的直径)

历届试题 大臣的旅费

问题描述
很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。
聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。
J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式
输入的第一行包含一个整数n(n<=10000),表示包括首都在内的T王国的城市数
城市从1开始依次编号,1号城市为首都。
接下来n-1行,描述T国的高速路(T国的高速路一定是n-1条)
每行三个整数Pi, Qi, Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。

输出格式
输出一个整数,表示大臣J最多花费的路费是多少。

样例输入1
5
1 2 2
1 3 1
2 4 5
2 5 4
样例输出1
135

样例分析:大臣J从城市4到城市5要花费135的路费。



——分割线之初入江湖——



分析:
读完题目后多少都有点懵(因为在蓝桥杯中,这种带故事背景的题,其描述往往都有点牛头不对马嘴的感觉),所以这里我们的第一件事依然是剖析题意
题目的大概意思是,给出一个带权值的图(该图保证无环路),然后让你在这个图中求出距离最远的两个节点之间的距离。当然,我们的任务在求出这个距离之后并未结束,我们还需要通过这个距离来求出旅行这段路的费用。
关于路费和距离,题目给的描述是“在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多”,也就是说在走第1千米到第2千米中的路费是1+10=11;第2千米到第3千米中的路费是2+10=12;第3千米到第4千米中的路费是3+10=13……其实这样的描述不好让人理解,因为“第x千米到第x+1千米”给我们的感觉并不是点到点,我们更喜欢这样的描述:
从点0km到点1km的这一千米路费为11;从点1km到点2km的这一千米路费为12……
其实你可以这么看每段路的路费:这是一个首项为11,公差为1的等差数列。即:

路费: 10+1  10+2  10+3  10+4  10+5 …… 10+(n-2)  10+(n-1)  10+n
序号:   1     2     3     4     5  ……   n-2       n-1       n

(即:第一个1千米路费为11、第二个1千米路费为12、……、第n个1千米的路费为10+n)
接着我们计算路费:
当你走了1千米的时候,你的路费就是这个数列中的第一项11
当你走了2千米的时候,你的路费就是这个数列中的前两项之和11+12=23
当你走了3千米的时候,你的路费就是这个数列中的前三项之和11+12+13=36
……
当你走了n千米的时候,你的路费就是这个数列中的前n项之和,即:
11+12+13+……(10+n-1)+(10+n)
=(10+1)+(10+2)+(10+3)+……+(10+n-1)+(10+n)
=(10+10+……10)+(1+2+3+……+n-1+n)
=10 ╳ n + n ╳ (1+n) / 2



——分割线之艰难磨砺——



路费的问题解决完了,接下来我们的重点回到“求距离最远的两个节点之间的距离”上
在以前的题目中,我们见过太多的求最短路径,求最少代价,花费最少时间……等字样的题,它们的共同特点是求最小值,此时我们常常会用一套广搜(有些也能用深搜)来伺候它。现在突然来一个求最大值的题,多少还是有点吃惊的。不过虽然题千变万化,但是算法的灵活性却为这些变化提供了坚实的基础,使得我们总能以不变应万变。
为了便于分析,下面我以一个例子来进行分析:
例图
上图给出了一个样例,其中共有5座城市(1为首都),我们来思考怎么求出这里面距离最远的两个城市?
我相信大部分同学的第一反映是暴力枚举:
枚举图中的每个点作为起点,然后通过dfs得到在该起点下同往其余n-1个节点的距离,然后再在这所有的距离中取出最大值即可
想法是很美好的,可是往往数据范围不会允许我们那么做,必定超时

网上有的博主给出了这样的思路:
由于每个点到点1的路径是唯一的,而求出任意两个点的最大距离,可以转化为求两个距离点1最远的点
对于这个结论,乍一看挺对的,但很遗憾的是这样的关于最大距离的转化实际上是不正确的
比如在我给出的例图中,距离点1最远的两个点分别是节点5(路径为1-3-5,距离为9)和节点6(路径为1-3-6,距离为6),但是这两个点之间的实际路径是(5-3-6),长度为7。导致出现这样的情况的原因是,在路径1-3-5和路径1-3-6中,其都含有公共路径1-3,使得在计算点5和点6之间的距离时,其会将公共部分忽略,而直接将两点间的最短路径作为最终的路径(即5-3-6),从而得出了距离点1最远的两个点之间的距离为5+2=7的结果,显然这样是不正确的

而解决本题的正确方法是利用树的直径算法
树的直径是指树上距离最远的两点间的距离(显然这个树的边须为带权边),它在树上问题上有许多应用,往往通过树的直径的性质可以将一个高时间复杂度的解法变为线性求解。
树的直径算法主要有两个,下面给出利用两次DFS(或BFS)求树的直径算法的做法:
1.从任意节点出发,通过DFS(或BFS)对树进行一次遍历,求出与出发点距离最远的节点记为p;
2.从节点p出发,通过DFS(或DFS)再进行一次遍历,求出与p距离最远的节点,记为q
则从p到q的路径就是树的一条直径

下面给出一个证明,为什么两次DFS(或BFS)寻找最远节点能够得到树的直径
首先要知道,若我们确定了直径的一个端点p,那么我们再以p为起点进行DFS(或BFS)寻找到的最远点q就一定是直径的另一个端点。否则这就与树的直径的定义相悖。所以问题的关键在于,为什么以任何一个节点作为起点寻找到的最远点会是直径的一个端点呢?
我们还是以这个图作为例子(已知最远路径为4-2-1-3-5,即树的直径的两个端点分别为4和5)

例图
假设你选取点1作为起点进行DFS以寻找最远点,你会找到点5(路径为1-3-5,距离为9),这确实是树的直径的其中一个端点;
假设再选取点2作为起点进行DFS以寻找最远点,你会找到点5(路径为2-1-3-5,距离为11),这也确实是树的直径的其中一个端点;
假设再选取点3作为起点进行DFS以寻找最远点,你会找到点4(路径为3-1-2-4,距离为9),这也确实是树的直径的其中一个端点;
假设再选取点6作为起点进行DFS以寻找最远点,你会找到点4(路径为6-3-1-2-4,距离为11),这也确实是树的直径的其中一个端点;
……
推广到其他图中也能得到同样地结果
出现上述情况的原因是:树的直径的定义告诉我们,若p不是直径的一端,那总能找到一条更长的链
于是可以得出结论:无论选取那个点作为DFS的起点,从这里出发都必然会找到树的直径中的一个端点,且被找到的这个端点是动态的(随你选择的起点而变化)

而当我们得到了距离最远的两个点后,就能根据这个距离得到路费了



——分割线之炉火纯青——



下面直接给出本题的完整代码:

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

const int MAX=10010;		//预设的最多城市数
int maxFarNode;				//maxFarNode存放的是从指定节点出发所能到的最远节点
int maxLen=-1;				//maxLen用于指示从指定节点出发所能到的最远距离
bool vis[MAX];				//用于指示某个点是否被访问过 
struct Node{
	int child,length;
	Node(int a,int b){
		child=a,length=b;
	}
};
vector<Node> v[MAX];
void dfs(int node,int nowLen)
{
	if(vis[node]) return;
	vis[node]=true;
	for(int i=0;i<v[node].size();i++)
	{
		int child =v[node][i].child;
		int length=v[node][i].length;
		if(vis[child]) continue;
		if(nowLen+length > maxLen){
			maxLen = nowLen+length;			//更新最大值 
			maxFarNode = child;				//更新最大值所在的节点位置 
		}
		dfs(child,nowLen+length); 
	}
}

int main()
{
	int n;
	cin>>n;
	for(int i=1;i<n;i++){
		int x,y,len;
		cin>>x>>y>>len;
		v[x].push_back(Node(y,len));
		v[y].push_back(Node(x,len));
	}
	dfs(1,0);
	memset(vis,false,sizeof(vis));
	maxLen=-1;
	dfs(maxFarNode,0);
	cout<<(maxLen*10+maxLen*(1+maxLen)/2)<<endl;
	return 0;
}
发布了38 篇原创文章 · 获赞 75 · 访问量 9522

猜你喜欢

转载自blog.csdn.net/the_ZED/article/details/104174375