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

历届试题 大臣的旅费

问题描述

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

输入格式

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

输出格式

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

样例输入1

5
1 2 2
1 3 1
2 4 5
2 5 4

样例输出1

135


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



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


读完题目后多少都有点懵(因为在蓝桥杯中,这种带故事背景的题,其描述往往都有点牛头不对马嘴的感觉),所以这里我们的第一件事依然是剖析题意。

题目的大概意思是,给出一个带权值的图(该图保证无环路),然后让你在这个图中求出距离最远的两个节点之间的距离。当然,我们的任务在求出这个距离之后并未结束,我们还需要通过这个距离来求出旅行这段路的费用。

关于路费和距离,题目给的描述是 “在走第 x x x 千米到第 x + 1 x+1 x+1千米这一千米中( x x x是整数),他花费的路费是 x + 10 x+10 x+10这么多”,也就是说在走第 1 千米到第 2 千米中的路费是1+10=11;第 2 千米到第 3 千米中的路费是 2+10=12;第 3 千米到第 4 千米中的路费是 3+10=13……其实这样的描述不好让人理解,因为 “第 x x x 千米到第 x + 1 x+1 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 ( n + 1 ) 2 \begin{align*} &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 + \frac{n(n+1)}{2} \end{align*} 11+12+13+……(10+n1)+(10+n)=(10+1)+(10+2)+(10+3)+……+(10+n1)+(10+n)=(10+10+……10)+(1+2+3+……+n1+n)=10n+2n(n+1)



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


路费的问题解决完了,接下来我们的重点回到 “求距离最远的两个节点之间的距离”上。

在以前的题目中,我们见过太多的求最短路径,求最少代价,花费最少时间……等字样的题,它们的共同特点是求最小值,此时我们常常会用一套广搜(有些也能用深搜)来伺候它。现在突然来一个求最大值的题,多少还是有点吃惊的。不过虽然题千变万化,但是算法的灵活性却为这些变化提供了坚实的基础,使得我们总能以不变应万变。

为了便于分析,下面我以一个例子来进行分析:
例图

上图给出了一个样例,其中共有 5 座城市(1 为首都),我们来思考怎么求出这里面距离最远的两个城市?

我相信大部分同学的第一反映是暴力枚举:

枚举图中的每个点作为起点,然后通过 dfs 得到在该起点下同往其余 n − 1 n-1 n1 个节点的距离,然后再在这所有的距离中取出最大值即可。想法是很美好的,可是往往数据范围不会允许我们那么做,必定超时。

网上有的博主给出了这样的思路:

由于每个点到点 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 p p
  2. 从节点 p p p 出发,通过 DFS(或 DFS)再进行一次遍历,求出与 p p p 距离最远的节点,记为 q q q

则从 p p p q q q 的路径就是树的一条直径

下面给出一个证明,为什么两次 DFS(或 BFS)寻找最远节点能够得到树的直径。

首先要知道,若我们确定了直径的一个端点 p p p,那么我们再以 p p p 为起点进行 DFS(或 BFS)寻找到的最远点 q q 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 p 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;
}
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theSerein

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值