训练军队(bfs求树的直径)

训练军队题解

题目描述

韩信所在的军队共 n n n个基地(编号 1 − n 1-n 1n)每个基地恰好由 n − 1 n−1 n1条道路连接,每条道路 i i i都有花费的时间 t i m e i time_i timei,如果一个基地只有一条道路与其他基地相连,则该基地会用来囤积粮食。
训练军队时,军队前进的速度恒定,韩信总是选择两个相距最远的粮食基地,并从这两个基地中的一个基地到达另外一个粮食基地,对于走过的每一条道路,军队都会修整和修复道路,这使得下一次途径该道路时花费的时间减半
每一天韩信都会举行两次训练,请你计算两次训练军队花费的总时间为多少?

输入描述

第一行包含整数 n n n。接下来 n − 1 n−1 n1 行,每行包含三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci表示点 a i a_i ai b i b_i bi之间存在一条道路,此条道路花费的时间为 c i c_i ci

输出描述

输出一个小数,表示训练军队花费的总时间,结果保留两位小数

样例输入

6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7

样例输出

38.50

数据范围

1 ≤ n ≤ 10000 1≤n≤10000 1n10000
1 ≤ a i , b i ≤ n 1≤a_i,b_i≤n 1ai,bin
0 ≤ c i ≤ 100000 0≤c_i≤100000 0ci100000

时间限制:1秒 内存限制:128M

样例解释

在这里插入图片描述
第一次:训练军队路径为 5 − 1 − 6 − 3 5-1-6-3 5163 ,道路上花费的时间为22

5 − 1 、 1 − 6 、 6 − 3 5-1、1-6、6-3 511663这三条路径时间减半

在这里插入图片描述

第二次:训练军队路径为 4 − 1 − 6 − 2 4-1-6-2 4162 ,道路上花费的时间为16.5

答案就是两次训练军队时间之和,22+16.5=38.5,保留小数为38.50。

解题思路

因为此题要求改变边权,所以使用bfs解题。

大致思路:
1.使用链式前向星存储。
2. bfs求直径
3. 权值减半
4. 使用修改过的权值,再bfs求一次直径
5. 将两次直径加和保留小数输出。

具体思路:
1.链式前向星存储。

  • 一定要注意 w w w(权值数组)设为 d o u b l e double double类型,因为权值减半后有小数。
void add(int a,int b,int c)
{
	ver[idx]=b,w[idx]=c,nxt[idx]=head[a],head[a]=idx++;
}
//...
memset(head,-1,sizeof(head));
int a,b,c;
cin>>n;
for(int i=1;i<n;i++)
{
	cin>>a>>b>>c;
	add(a,b,c);
	add(b,a,c);
}`

2.使用广度优先搜索求树的直径

  • v i s vis vis为判重数组, d i s dis dis为距离数组, p r e pre pre为节点的前驱。
  • 建队列,将 v i s vis vis设为 0 0 0,将 d i s dis dis设为 0 x 3 f 0x3f 0x3f
  • r o o t root root节点入队,将 v i s [ r o o t ] vis[root] vis[root]设为已访问,将 d i s [ r o o t ] dis[root] dis[root]设为 0 0 0
  • 如果队列不空,取出队头元素,名为 t m p tmp tmp,并进行一次出队。
  • 遍历边集,设 y y y为边的终点, z z z为边权。
  • 如果 y y y没有被遍历过,就更新 v i s y vis_y visy 1 1 1,将 y y y点入队,将 d i s y dis_y disy设为 d i s [ t m p ] + z dis[tmp]+z dis[tmp]+z
  • p r e y pre_y prey设为当前遍历到的 i i i
  • m a x max max − 1 -1 1,设 n o d e node node 0 0 0
  • 循环遍历 d i s dis dis数组,若 d i s i dis_i disi大于 m a x max max,则让 m a x max max等于 d i s i dis_i disi,并让 n o d e node node等于 i i i
  • b f s bfs bfs函数返回 n o d e node node
int bfs(int root)
{
   	queue<int>q;
   	memset(vis,0,sizeof(vis));
   	memset(dis,0x3f,sizeof(dis));
   	q.push(root);
   	vis[root]=1;
   	dis[root]=0;
   	while(!q.empty())
   	{
   		int tmp=q.front();
   		q.pop();
   		for(int i=head[i];~i;i=nxt[i])
   		{	
   			int y=ver[i];
   			double z=w[i];
   			if(!vis[y])
   			{
   				vis[y]=1;
   				q.push(y);
   				dis[y]=dis[tmp]+z;
   				pre[y]=i;
   			}
   		}
   }
   int max=-1;
   int node=0;
   for(int i=1;i<=n;i++)
   {
   		if(dis[i]>max)
   		{
   			max=dis[i];
   			node=i;
   		}
   }
   return node;
}

3.将直径经过的边权值减半

  • s u m 1 sum1 sum1 s u m 2 sum2 sum2为两次 b f s bfs bfs(即第一次求直径)的结果。
  • a n s ans ans d i s [ s u m 2 ] dis[sum2] dis[sum2]的值。
  • 设变量 i i i s u m 2 sum2 sum2的前驱,变量 w w ww ww w i w_i wi的值。
  • 使用 w i = w i 异或 1 w_i=w_i异或1 wi=wi异或1的方法,找到反向边(树是一个无向连通图,必有反向边)。
  • 将这两条边权值除以 2.0 2.0 2.0
int sum1=bfs(1);
int sum2=bfs(sum1);
double ans=dis[sum2];
while(sum2!=sum1)
{
	int i=pre[sum2];
	double ww=w[i];
	w[i]=w[i^1]=ww/2.0;
	sum2=ver[i^1];
}

4.再次求直径。

sum1=bfs(1);
sum2=bfs(sum1);

5.将 a n s ans ans进行加和,并保留小数输出。

ans+=dis[sum2];
printf("%.2lf",ans);

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int head[N];
int ver[2*N];
int nxt[2*N];
int idx=0;
double w[2*N];
bool vis[N];
double dis[N];
int pre[N];
int n;
void add(int a,int b,int c)
{
	ver[idx]=b,w[idx]=c,nxt[idx]=head[a],head[a]=idx++;
}
int bfs(int root)
{
	queue<int>q;
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	q.push(root);
	vis[root]=1;
	dis[root]=0;
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		for(int i=head[i];~i;i=nxt[i])
		{
			int y=ver[i];
			double www=w[i];
			if(!vis[y])
			{
				vis[y]=1;
				q.push(y);
				dis[y]=dis[tmp]+www;
				pre[y]=i;
			}
		}
	}
	int max=-1;
	int node=0;
	for(int i=1;i<=n;i++)
	{
		if(dis[i]>max)
		{
			max=dis[i];
			node=i;
		}
	}
	return node;
}
int main() 
{
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for(int i=1;i<n;i++) 
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	int sum1=bfs(1);
	int sum2=bfs(sum1);
	double ans=dis[sum2];
	while(sum2!=sum1)
	{
		int i=pre[sum2];
		double ww=w[i];
		w[i]=w[i^1]=ww/2.0;
		sum2=ver[i^1];
	}
	sum1=bfs(1);
	sum2=bfs(sum1);
	ans+=dis[sum2];
	printf("%.2lf",ans);
	return 0;
}

谢谢阅读。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值