魔力树(数学证明)

27 篇文章 0 订阅
13 篇文章 0 订阅

题目:

题目描述

GM有一棵魔力树,即无向边连接的树。树上每个节点都分配了一个魔力值Xi。路径的魔力值被定义为该路径上节点的魔力值的乘积除以该路径上节点的数量。例如:路径上有两个点,魔力值分别是3和5,那么这条路径的魔力值就为15/2,在给定的树中,找到具有最小魔力值的路径并输出该路径的魔力值。

输入格式

第一行输入包含整数 N(1≤N≤1e6)。表示树中的节点数。 接下来N−1行中的每一行包含两个整数,Ai 和Bi (1≤Ai,Bi≤N)表示每条边连接的两个节点的编号。 接下来N行中的每一行包含一个整数Xi(1≤Xi≤1e9 )。表示第i个节点的魔力值。

输出格式

以分数P/Q的形式输出具有最小魔力值的路径的魔力值(P和Q是互质的整数)。 保证P和Q小于1e18。

样例

样例输入

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

样例输出

1/2

分析:

经过证明可得(因为还不知道是怎么证的)整条路径中只会有1个2的点权的点或没有,且其它的点权都是1的时候是最小的

那么就可用树形dp来写了

记录dp[i]为以i为子树的点全为1的最长的链(路径)

dp2[i]为以i为子树的点只有一个点为2的最长链

那么转移其实会有三种情况

1.把dp[i]与dp2[i]相连

如果dp2[i]与dp[i]找的是同一条链,那么就需要找的dp[]的次长链才可以

2.把次长链与dp2[i]相连

3.把次长链与dp[i]相连

当然,dp[]与dp2[]的转移公式很好写了

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
#define ll long long
const int MAXN = 1e6 + 3;
int ma[MAXN];
int n;
vector <int>G[MAXN];
int sum ;int minn = 1e9 + 3 , mb = 1;//记录分数
int dp2[MAXN] , dp[MAXN];
void check( int x , int y ){//比较大小
    if( (double) x / y < (double) minn / mb )
        minn = x , mb = y;
}
void dfs( int x , int f ){
    int n1 = 0 , n2 = 0 , n3 = 0;//n1是dp[i]的最大值,n2是次大值,n3是dp2[i]的最大值
    for( int i = 0; i < G[x].size() ; i ++){
        int v = G[x][i];
        if(v == f ) continue;
        dfs( v , x );
        if( ma[x] == 1 ){
            dp[x] = max( dp[x] , dp[v] +1 );
            dp2[x] = max( dp2[x] , dp2[v] + 1 );//如果本身就是,则可以把x算上
        }
        if( ma[x] == 2 ){
            dp2[x] = max( dp2[x] , dp[v] + 1 );
        }
        if( dp[n1] < dp[v] ) n2 = n1 , n1 = v;
        else if( dp[n2] < dp[v] ) n2 = v;
        if( dp2[n3] < dp2[v] ) n3 = v;//转化
    }
    if( ma[x] == 1 ){
        if( n1 == n3 )
            check( 2 , dp[n2]+dp2[n3]+1 );
        else{
            check( 2 , dp[n1]+dp2[n3]+1 );
        }
        check( 1 , dp[n2]+dp[n1]+1 );
        dp[x] = max( dp[x] , 1 );
    }
    if( ma[x] == 2 ){
        check( 2 , dp[n2]+dp[n1]+1 );
        dp2[x] = max( dp2[x] , 1 );
    }
}
int gcd( int a,  int b ){//要约分
	if( b == 0 )
		return a;
	return gcd( b , a % b );
}
int main()
{
    scanf( "%d" , &n );
    for( int i = 1;  i < n ; i ++ ){
        int x , y;
        scanf( "%d%d" , &x , &y );
        G[x].push_back(y );
        G[y].push_back(x);
    }
    for( int i = 1 ;i <= n ; i ++ ){
        scanf( "%d" , &ma[i] );
        minn = min(minn , ma[i] );
    }//预处理,如果整个图里面没有1,则找最小点权
    dfs( 1 , 0 );
    int e = gcd( minn , mb );
	printf( "%d/%d" , minn / e , mb / e );
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值