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

洛谷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}; queue 
        
          p[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; j 
         
           0) 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 
          
        
       
       
      
      
     
     
    
    

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值