软件学院3.21天梯模拟 L3-1 直直直径 (30 分)(树的直径,DFS,BFS,树形dp)

题目:

Keven现在有一棵树,现在Keven想知道在这颗树上任取两点,他们的距离的最大值是多少,Keven不会做这个题目,于是请教聪明的你,如果你帮助他解决这个问题,他将会让你的排名上升。

树中两点之间的距离定义为连接两点的路径边权之和。并且每条路径经过的次数不能超过1次。

输入格式:

第一行给出一个数字N,表示树的节点个数。(树的节点为1-N)

接下来N-1行,每行给出三个数字U,V,W,表示点U与点V之间有一条权值为W的路径。

(N<200000,W<100000000)

输出格式:

在一行中输出树上任意两点距离的最大值。

输入样例:

在这里给出一组输入。例如:

4
1 2 5
1 3 6
1 4 7

输出样例:

在这里给出相应的输出。例如:

13

案例解释:

第三个点到第四个点的距离最大,最大值为13。

案例.png

思路:

我们首先看数据范围,N < 2*10^{5},那么我们只能采用O(n)O(nlogn)的做法

再看题意,给定一棵树,要求这棵树任意两个节点的最长距离,最能想到的就是暴力了,枚举每一个点作为起点,然后DFS或者BFS求从它出发能够达到的最大距离,再对所有的点能到达的最大距离取max,但这个算法的时间复杂度是O(n^{2}),因为有n个点,每个点计算它的最长距离是O(n)的,那这个做法显然会超时

其实这道题是求树的直径的模板题,求解分为两种方法,一种通过两次遍历求解,一种一次遍历求解,复杂度都是O(n)

两次遍历:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是是的直径

证明如下:

①若P已经在直径上,根据树的直径的定义可知Q也在直径上且为直径的一个端点

②若P不在直径上,我们用反证法,假设此时WQ不是直径,AB是直径

--->若AB与PQ有交点C,由于P到Q最远,那么PC+CQ>PC+CA,所以CQ>CA,易得CQ+CB>CA+CB,即CQ+CB>AB,与AB是直径矛盾,不成立,如下图(其中AB,PQ不一定是直线,画成直线是为了方便):

--->若AB与PQ没有交点,M为AB上任意一点,N为PQ上任意一点。首先还是NP+NQ>NQ+MN+MB,同时减掉NQ,得NP>MN+MB,易知NP+MN>MB,所以NP+MN+MA>MB+MA,即NP+MN+MA>AB,与AB是直径矛盾,所以这种情况也不成立,如下图:

两遍遍历代码: 

//L3-1 直直直径 (30 分)(两遍遍历)
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

#define x first
#define y second

using namespace std;
typedef long long ll;
const int N = 2e5 + 10;

ll ans;
int n , a , b , w , x;
vector<pair<int , int>>G[N];                    //数组的两维分别的能够到达的点,和对应的权值

void dfs(int u , ll len , int &x , int front)   //u为当前点,len为从起点到该点的长度,x为最远能够到达的点,front表示当前点的父节点
{
	if(len > ans)                               //如果最大长度小于当前长度,则更新最大长度和最远能到达的点
	{
		ans = len;
		x = u;
	}
	
	for(int i = 0 ; i < G[u].size() ; i++)      //遍历每一个能够到达的点
	{
		auto t = G[u][i];
		if(t.x != front)                        //不能走回头路
			dfs(t.x , len + t.y , x , u);
	}
}

int main()
{
	cin>>n;
	for(int i = 0 ; i < n - 1 ; i++)
	{
		cin>>a>>b>>w;
		G[a].push_back({b , w});
		G[b].push_back({a , w});
	}
	
	dfs(1 , 0 , x , -1);                        //从任意一点出发,x为最远能够到达的点
	dfs(x , 0 , a , -1);                        //再从x出发,寻找最远能够到达的点
	cout<<ans<<endl;
	return 0;
}

 一遍遍历:我们在数据结构中都求过二叉树的高度,通过递归左子树高度和右子树高度,最后取最大值就是树的高度,在这里我们同样可以借助这个思想,但是这里可不是二叉树了,而是n叉树,其中我们要求的最大距离就是,已该节点为根节点的最长子树高度加上次长子树高度,可以想象成是一个倒“V”字型,这种方法我们需要维护某一节点为根节点的子树高度最大值和次大值

一遍遍历代码:

//L3-1 直直直径 (30 分)(一遍遍历)
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

#define x first
#define y second

using namespace std;
typedef long long ll;
const int N = 2e5 + 10;

ll ans;
int n , a , b , w;
vector<pair<int , int>> G[N];                //数组去存能到达的点,和对应的权值

ll dfs(int u , int front , ll len)           //传入当前节点,父节点,当前距离
{
	ll d1 = 0 , d2 = 0;                      //d1为最大值,d2为次大值,注意开long long
	for(int i = 0 ; i < G[u].size() ; i++)   //遍历每一个子树
	{
		if(G[u][i].x != front)               //不能走回头路否则超时
		{
			ll d = dfs(G[u][i].x , u , G[u][i].y);    //该子树的高度
			if(d >= d1)                      //如果比最大值都大,那么最大值和次大值都得更新
				d2 = d1 , d1 = d;
			else if(d > d2)                  //如果只比次大值大,则只更新次大值
				d2 = d;
		}		
	}
	
	ans = max(ans , d1 + d2);                //对当前的最大值和次大值取max
	return len + d1;
}

int main()
{
	cin>>n;
	for(int i = 0 ; i < n - 1 ; i++)
	{
		cin>>a>>b>>w;
		G[a].push_back({b , w});
		G[b].push_back({a , w});	
	}
	
	dfs(1 , -1 , 0);
	cout<<ans<<endl;
	return 0;	
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值