[COCI2016-2017#1] Mag 树形dp题解

题意

你将获得一棵由无向边连接的树。树上每个节点都有一个魔力值。

我们定义,一条路径的魔力值为路径上所有节点魔力值的乘积除以路径上的节点数。

例如,若一条路径包含两个魔力值分别为 3 , 5 3,5 3,5 的节点,则这条路径的魔力值为 3 × 5 / 2 = 7.5 3\times 5/2=7.5 3×5/2=7.5

请你计算,这棵树上魔力值最小的路径的魔力值。

输入格式

第一行输入包含整数 n ( 1 ≤ n ≤ 1 0 6 ) n(1≤n≤10^6) n(1n106)。表示树中的节点数。 接下来 n − 1 n−1 n1行中的每一行包含两个整数, a i a_i ai b i b_i bi ( 1 ≤ a i , b i ≤ n ) (1≤a_i,b_i≤n) (1ai,bin) 表示每条边连接的两个节点的编号。 接下来 n n n 行中的每一行包含一个整数 x i ( 1 ≤ x i ≤ 1 e 9 ) x_i(1≤x_i≤1e9 ) xi(1xi1e9)。表示第 i i i 个节点的魔力值。

输出格式

以分数 P / Q P/Q P/Q 的形式输出具有最小魔力值的路径的魔力值( P P P Q Q Q 是互质的整数)。 保证 P P P Q Q Q 小于 1 0 18 10^{18} 1018

样例

样例输入
5
1 2
2 4
1 3
5 2
2
1
1
1 
3
样例输出
1/2

分析

显然,我们选的这条链 1 1 1 要越多越好,现在有几种情况。

  1. 1   1   1   . . . x . . .   1   1   1 ( x ≤ 2 ) 1\ 1\ 1\ ... x...\ 1\ 1\ 1(x \leq 2) 1 1 1 ...x... 1 1 1(x2),令 m m m 为长的那一段 1 1 1 的长度, n n n 为短的那一段 1 1 1 的长度,则若我们要选 x x x,要满足 1 / m > 2 / ( m + n + 1 ) 1/m > 2/(m+n+1) 1/m>2/(m+n+1),即 m < n + 1 m < n + 1 m<n+1
  2. 1   1   1   . . . x . . .   1   1   1 ( x > 2 ) 1\ 1\ 1\ ... x...\ 1\ 1\ 1(x > 2) 1 1 1 ...x... 1 1 1(x>2),则 1 / m > x / ( m + n + 1 ) 1/m> x/(m+n+1) 1/m>x/(m+n+1)经枚举得此方程无解。
  3. 1   1   1   . . . 2   2...   1   1   1 ( x ≤ 2 ) 1\ 1\ 1\ ... 2\ 2...\ 1\ 1\ 1(x \leq 2) 1 1 1 ...2 2... 1 1 1(x2),则 1 / m > 4 / ( m + n + 2 ) 1/m>4/(m+n+2) 1/m>4/(m+n+2),即 3 m < n + 2 3m<n+2 3m<n+2,显然无解。
  4. 1   1   1   . . . x   y . . .   1   1   1 ( x , y > 2 ) 1\ 1\ 1\ ... x\ y...\ 1\ 1\ 1(x,y > 2) 1 1 1 ...x y... 1 1 1(x,y>2),显然无解。

综上,我们发现,这条链只能全是 1 1 1 或 有一个 2 2 2,但是还有一种特殊情况:若树中没有 1 1 1,我们取最小值就行了。
分析一波过后,我们会知道这题肯定能用 树形DP 做。

定义 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 为这个结点往下走(即往儿子的方向走)能形成全是 1 1 1 的链能经过的最多的点, d p [ i ] [ 1 ] dp[i][1] dp[i][1] 为这个结点往下走(即往儿子的方向走)能形成有一个 2 2 2 的链能经过的最多的点。
定义 g [ i ] [ 0 ] g[i][0] g[i][0] 为经过这个结点 而在它的子树中 能形成全是 1 1 1 的链能经过的最多的点, g [ i ] [ 1 ] g[i][1] g[i][1] 为经过这个结点 而在它的子树中 能形成有一个 2 2 2 的链能经过的最多的点。
(写的有点绕,意会吧。。。)

if(a[x] == 1) {
	g[x][0] = max(g[x][0], dp[x][0] + dp[Y][0]);
	g[x][1] = max(g[x][1], dp[x][0] + dp[Y][1]);
	g[x][1] = max(g[x][1], dp[x][1] + dp[Y][0]);
	dp[x][0] = max(dp[x][0], dp[Y][0]);
	dp[x][1] = max(dp[x][1], dp[Y][1]);
}
if(a[x] == 2) {
	g[x][1] = max(g[x][1], dp[x][1] + dp[Y][0]);
	dp[x][1] = max(dp[x][1], dp[Y][0]);
}

不想解释了,这就像求树的直径的两个数组。

代码(不用开long long)

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#define LL long long
using namespace std;
const int MAXN = 1e6 + 5;
const LL lof = 9e18;
int n, tot, Head[MAXN], Ver[MAXN << 1], Next[MAXN << 1], a[MAXN], minn = 2e9;
LL dp[MAXN][2], g[MAXN][2];
void add(int x, int y) {
	Ver[++ tot] = y;
	Next[tot] = Head[x]; Head[x] = tot;
}
void read(int &x) {
    bool f = 1;
    x = 0;
    char c = getchar();
    while(c < '0' || c > '9') {
        if(c == '-') f = 0;
        c = getchar();
    }
    while(c >= '0' && c <= '9') {
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }
    x = f ? x : -x;
}
void dfs(int x, int fa) {
	for(int i = Head[x]; i; i = Next[i]) {
		int Y = Ver[i];
		if(fa == Y) continue;
		dfs(Y, x);
		if(a[x] == 1) {
			g[x][0] = max(g[x][0], dp[x][0] + dp[Y][0]);
			g[x][1] = max(g[x][1], dp[x][0] + dp[Y][1]);
			g[x][1] = max(g[x][1], dp[x][1] + dp[Y][0]);
			dp[x][0] = max(dp[x][0], dp[Y][0]);
			dp[x][1] = max(dp[x][1], dp[Y][1]);
		}
		if(a[x] == 2) {
			g[x][1] = max(g[x][1], dp[x][1] + dp[Y][0]);
			dp[x][1] = max(dp[x][1], dp[Y][0]);
		}
	}
	if(a[x] == 1) {
		dp[x][0] ++; dp[x][1] ++;
		g[x][0] ++; g[x][1] ++;
	}
	if(a[x] == 2) {
		dp[x][1] ++; g[x][1] ++;
	}
}
int main() {
	int x, y;
	LL s, t;
	read(n);
	for(int i = 1; i < n; i ++) {
		read(x); read(y);
		add(x, y); add(y, x);
	}
	for(int i = 1; i <= n; i ++) {
		read(a[i]);
		minn = min(minn, a[i]);
	}
	s = 1e9; t = 1;
	int F = 0;
	dfs(1, -1);
	for(int i = 1; i <= n; i ++) {
		if(g[i][0]) {
			F = 1;
			if(1.0 * t < s * g[i][0]) {
				s = (LL)1; t = g[i][0];
			}
		}
		if(g[i][1]) {
			F = 1;
			if(2.0 * t < s * g[i][1]) {
				s = (LL)2; t = g[i][1];
			}
		}
	}
	if(s == 2) {
		if(!(t & 1)) {
			s >>= 1; t >>= 1;
		}
	}
	if(F) {
		printf("%lld/%lld", s, t);
	}
	else {
		printf("%d/%d", minn, 1);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值