(AcWing)蓝桥杯:大臣的旅费(对树直径求法的个人笔记)(1207)

题目:

有多个城市与王城相连,从王城到每个城市有且仅有一条路径(代表这是一个无环图,是一颗树的结构),每条路径都有长度作为它的权值,有这样一条收费规则:每从X km到X+1 km处,这1km需要花费(X+1)+10的钱。现在问有一个大臣要从某一个城市到另一个城市,问你最多需要多少钱。

样例:

输入:

第一行代表几个城市;

下面n-1行的3个数分别表示城市x到y之间有一条权值为z的路径(双向)

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

输出:

最后的费用:

135

分析:

首先,钱的花费明显与距离有直接的关系,走的路越长,就花的越多。那么我们就是要在一个图中去找到一条最长路。那么最长路也就是一颗树的直径,有一个方法可以算树的直径(直径可能不止一条,但这样求出来的绝对是其中一条):先从任意点a到离a最远的点b,再从b到离他最远的点c,那么b到c的距离就是最长的路径(树的直径之一)。原理可以用反证法,假设有另一条边才是最长边比b到c还长,无论什么情况下,都可以证明这条边不是最长边(都能找到一个更长的,所以矛盾)。

那么最长路求出来了,最后就是一个与路长有关的费用公式推理了,由于最后长度可能比较长,每次用循环推肯定是不行的。

费用公式推理:

首先:有题目可知从

0->1:1+10=11

0->2:2+10+1+10=23

0->3:3+10+2+10+1+10=36

那么很快规律就出来了,总长1km时费用为1+1*10;2km时为:1+2+2*10;3km时为:1+2+3+3*10;

那么容易得到长度:

n km时:1+2+3+...+n-1+n+n*10;(一个n倍10加上一个从1到n的等差数列前n项和)

等差数列前n项和的公式为:(a1+an)*n/2;(a1:首项;an:末项;n:项数)

所以n km的花费为:ans = n*10+(1+n)*n/2;

验算一下公式的正确性,由样例可得最长为9km,待入公式n中

ans=90+45=135(完全正确!)

贴代码!!!:

#include<iostream>
#include<algorithm>
#include<queue>
#include<utility>
#include<cstring>
#include<stack>
#include<cmath>
using namespace std;
typedef long long llint;

const int N=1e5+5;
llint n,node=0,head[N],vis[N];

struct Node{
	llint to,pre,weight;
}edge[2*N];
//结构体储存边:到哪里去;父亲在哪里;权值是多少。
void add(llint u,llint v,llint w){
	edge[node].to=v;
	edge[node].weight=w;
	edge[node].pre=head[u];
	head[u]=node++;
}
//链式前向星建边存图
llint ans,sum,str;//ans装最终最长的那条边,sum累加每条路径的长度来不断跟新ans,str是第一次dfs最远点的那个点的载体。
void dfs(llint u){
	if(ans<sum){
		str=u;
		ans=sum;//每次找到更远的点就跟新ans和str
	}
	for(llint i=head[u];i!=-1;i=edge[i].pre){//链式前向星遍历u的出边
		llint v=edge[i].to;
		llint w=edge[i].weight;
		if(!vis[v]){//没被走过
			vis[v]=1;//进入dfs
			sum+=w;
			dfs(v);
			sum-=w;
			vis[v]=0;
		}
	}
	return;
}
int main(){
	cin >> n;
	memset(head,-1,sizeof head);//链式前向星初始化
	for(llint i=1;i<n;i++){
		llint u,v,w;
		scanf("%lld%lld%lld",&u,&v,&w);
		add(u,v,w);//链式前向星存图
		add(v,u,w);//双向存图
	}
	ans=0;//记录答案
	sum=0;//用来记录当前路径上的权值
	vis[1]=1;//任选一点当作起点开始dfs树找离他最远的点
	dfs(1);
	vis[1]=0;//找到了后回来依次删除标记,因为后面str还要来一次dfs找最远
	ans=0;//重新初始化,因为递归回来的时候依次讲标记撤销了,所以vis不需要初始化
	sum=0;
	vis[str]=1;//第二次dfs最远点,找到最终长度。
	dfs(str);
	cout << ans*10+(ans+1)*ans/2 << endl;//运用公式代入最终长度计算答案。
	return 0;
}

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值