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

文章描述了一个关于计算军队训练过程中路径优化的问题,涉及基地之间的道路连接、道路花费时间和道路修复导致的时间减半。解题策略是使用BFS算法寻找树的直径,并在找到直径后更新道路权重,再次求解直径以得出总时间。样例展示了具体的计算过程和结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

训练军队题解

题目描述

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

输入描述

第一行包含整数 nnn。接下来 n−1n−1n1 行,每行包含三个整数 ai,bi,cia_i,b_i,c_iai,bi,ci表示点 aia_iaibib_ibi之间存在一条道路,此条道路花费的时间为cic_ici

输出描述

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

样例输入

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

样例输出

38.50

数据范围

1≤n≤100001≤n≤100001n10000
1≤ai,bi≤n1≤a_i,b_i≤n1ai,bin
0≤ci≤1000000≤c_i≤1000000ci100000

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

样例解释

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

5−1、1−6、6−35-1、1-6、6-3511663这三条路径时间减半

在这里插入图片描述

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

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

解题思路

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

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

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

  • 一定要注意www(权值数组)设为doubledoubledouble类型,因为权值减半后有小数。
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.使用广度优先搜索求树的直径

  • visvisvis为判重数组,disdisdis为距离数组,preprepre为节点的前驱。
  • 建队列,将visvisvis设为000,将disdisdis设为0x3f0x3f0x3f
  • rootrootroot节点入队,将vis[root]vis[root]vis[root]设为已访问,将dis[root]dis[root]dis[root]设为000
  • 如果队列不空,取出队头元素,名为tmptmptmp,并进行一次出队。
  • 遍历边集,设yyy为边的终点,zzz为边权。
  • 如果yyy没有被遍历过,就更新visyvis_yvisy111,将yyy点入队,将disydis_ydisy设为dis[tmp]+zdis[tmp]+zdis[tmp]+z
  • preypre_yprey设为当前遍历到的iii
  • maxmaxmax−1-11,设nodenodenode000
  • 循环遍历disdisdis数组,若disidis_idisi大于maxmaxmax,则让maxmaxmax等于disidis_idisi,并让nodenodenode等于iii
  • bfsbfsbfs函数返回nodenodenode
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.将直径经过的边权值减半

  • sum1sum1sum1sum2sum2sum2为两次bfsbfsbfs(即第一次求直径)的结果。
  • ansansansdis[sum2]dis[sum2]dis[sum2]的值。
  • 设变量iiisum2sum2sum2的前驱,变量wwwwwwwiw_iwi的值。
  • 使用wi=wi异或1w_i=w_i异或1wi=wi异或1的方法,找到反向边(树是一个无向连通图,必有反向边)。
  • 将这两条边权值除以2.02.02.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.将ansansans进行加和,并保留小数输出。

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;
}

谢谢阅读。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值