洛谷P1268 树的重量(构造法)

本文详细解析洛谷P1268树的重量问题,介绍两种解题思路:构造法和贪心算法。通过实例说明如何根据物种间的距离矩阵重构进化树及其总重量。

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

洛谷P1268 树的重量(构造法)

题目描述

树可以用来表示物种之间的进化关系。一棵“进化树”是一个带边权的树,其叶节点表示一个物种,两个叶节点之间的距离表示两个物种的差异。现在,一个重要的问题是,根据物种之间的距离,重构相应的“进化树”。
令N={1..n},用一个N上的矩阵M来定义树T。其中,矩阵M满足:对于任意的i,j,k,有M[i,j] + M[j,k] >= M[i,k]。树T满足:
1.叶节点属于集合N;
2.边权均为非负整数;
3.dT(i,j)=M[i,j],其中dT(i,j)表示树上i到j的最短路径长度。
如下图,矩阵M描述了一棵树。

树的重量是指树上所有边权之和。对于任意给出的合法矩阵M,它所能表示树的重量是惟一确定的,不可能找到两棵不同重量的树,它们都符合矩阵M。你的任务就是,根据给出的矩阵M,计算M所表示树的重量。下图是上面给出的矩阵M所能表示的一棵树,这棵树的总重量为15。

输入输出格式

输入格式:

输入数据包含若干组数据。每组数据的第一行是一个整数n(2<n<30)。其后n-l行,给出的是矩阵M的一个上三角(不包含对角线),矩阵中所有元素是不超过100的非负整数。输入数据保证合法。
输入数据以n=0结尾。

输出格式:

对于每组输入,输出一行,一个整数,表示树的重量。

输入输出样例

输入样例:

5
5 9 12 8
8 11 7
5 1
4
4
15 36 60
31 55
36
0

输出样例:

15
71

解题分析1

该方法能通过前5个测试点,但第六个没有通过,由于没有测试数据,不知哪儿出问题了。这里请教大家了。
假设给定的矩阵为m[N][N],在构造树时,主要考虑确定每两个顶点(树的叶节点)之间需要增加几个点以及如何增加。可以采取如下方法:
1、以点1和2为例,如果某个结点k到点1和2的距离分别为m[1][k]和m[2][k],则差d = m[1][k] - m[2][k],因此,在1和2之间必须增加一个结点p,该结点将边1和2分成两部分,这两部分的差为d,通过计算(简单的二元方程组)可以得到,p到点1的距离为d1=(m[1][2] + d)/2(题目中的条件保证该数为正数)。
2、如果另一个点k1到1和2的距离差也是d,则k1也在从p引出的一个分支上(如图1中的3,4,6)。
3、因此,可以得到:假设到点1和点2的距离的差有j个不同的值,则应该在该边上增加j个不同的中间点(图1中的j为2)。
4、如果1和2中间增加的某个点只有另外一个分支(如图1中的5),则可以直接将结果加上:m[1][k2] - d1。
5、如果1和2中间增加的某个点另外有多个分支(3,4,6),则将第一个分支(编号最小,3)按照4的方式对结果加上相应的值(如图2),对于其他分支(4和6),则必须在1和k2的边上增加一些新的结点,处理方式与点1和点2的处理方式相同(可以递归实现)。注意该分支上处理的点只能是与它同在一个分支上的点,例如处理1,3时,只能考虑4和6。
6、初始化:求每一个顶点k到第一个点与另一个顶点k1(k1>k)之间距离的差值,并将该差值放到一个队列中。


#include 
#include 
#include 
#include 
#include 
#define N 32
using namespace std;
int n, m[N][N] = {0}, ans, used[N] = {0};
queuep[N][101];
void get_i(int &x){
	x = 0;
	char ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)){
		x = x*10 + ch - '0';
		ch = getchar();
	}
} 
void ini(){
	int i, j, k, x;		
	// 计算在路径0-j上有哪些分叉点,并将相同的分叉点放到队列q中,编号较小的先入队 
	for(j=1; j0)			
					resolve(j, a1, n2);//处理0-j上的分叉点 
				break;
			}			
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	int i, j, a[N];
	get_i(n);
	while(n){
		memset(m, 0, sizeof(m));
		memset(used, 0, sizeof(used));
		for(i=0; i

解题分析2:

采用贪心算法,从小到大(编号)将每一个顶点加入树中,在加入某个顶点k时,考虑所有可能增加的重量中最小值。
具体做法是:
1、先将1和2加入到树中。
2、3可以直接加入(如图3),先计算1到3和2的距离的差值d=m[1][3]-m[1][2],则新增加的值为(m[2][3]+d)/2(结点3到新增结点之间的距离)。
3、对于结点4,先计算1到4和2的距离的差值,并得到新增加的值x1(方法同第二步),再计算1到4和3的距离的差值,并得到新增加的值x2,选择x1和x2中较小的加入到结果(如图4,此时x2较小)。

4、对于结点k,同样,计算1到k和k1(k1<k, k1>1)距离的差值,并得到新增加的值,将这些值中最小的值加入到结果中。


#include
#include
#include
#include
#include

using namespace std;
#define N 32
#define INF 0x7fffffff
int d[N][N];
void get_i(int &x){
	x = 0;
	char ch = getchar();
	while(!isdigit(ch)) ch = getchar();
	while(isdigit(ch)){
		x = x*10 + ch - '0';
		ch = getchar();
	}
} 
int main(){
	int n, i, t, ans;
	get_i(n);
    while(n){            
        for(i=0;i

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值