【魔力树】题解

/*
dp的状态:
dp1:存储以当前节点为链的一端时魔力值为1时链的最长长度 
dp2:存储以当前节点为链的一端时魔力值为2时链的最长长度 
考虑dp的转移 
节点x得出的答案由其自身与两个不同的子节点i,j组成
*/ 
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
int n , a[MAXN] , dp1[MAXN] , dp2[MAXN] , m , head[MAXN] , cnt;
struct node{
	int to , nxt;
}edge[MAXN << 1];
void add(int u , int v) {//本题采用链式前向星存储 
	edge[++ cnt].to = v;
	edge[cnt].nxt = head[u];
	head[u] = cnt;//模板 
}
int p = 1e9 , q = 1;
int minn = 0x3f3f3f3f;
void update(int x , int y) {//交叉相乘,更新答案 
	if (p * y > q * x) {
		p = x;
		q = y;
	}
}
void Dp(int now , int fa) {
	int f1 = 0 , f2 = 0 , s1 = 0 , s2 = 0;
	/* 
		f1表示now的节点中dp1最大的节点 
		f2表示now的节点中dp1次大的节点 
		s1表示now的节点中dp2最大的节点 
		s2表示now的节点中dp2次大的节点 
	*/ 
	for (int i = head[now] ; i ; i = edge[i].nxt) {//枚举子节点 
		int v = edge[i].to;
		/*
			以下术语中:
			1链表示魔力值为1的链 
			2链表示魔力值为2的链 
		*/
		if (v != fa) {//该图是无向图,记得防止重复遍历 
			Dp(v , now);//向节点搜索 
			if (dp1[v] > dp1[f1]) {//如果该节点的最长1链比当前最长1链长 
				f2 = f1;//那么,当前最大值就变为次大值 
				f1 = v;//更新最大值 
			} else if (dp1[v] > dp1[f2]) {//否则,该节点的最长1链没有资格更新当前最长1链
				f2 = v;//更新次长1链 
			}
			if (dp2[v] > dp2[s1]) {//同理,以相同的方法更新最长2链和次长2链,不赘述了 
				s2 = s1;
				s1 = v;
			} else if (dp2[v] > dp2[s2]) {
				s2 = v;
			}
		}
	}  
	//对当前节点的魔力值进行分讨 
	if (a[now] == 1) {//该节点魔力值为1 
		dp1[now] = dp1[f1] + 1;//则now的最长1链为它的子节点中最长的1链 +1(自己也要算进去) 
		update(1 , dp1[f1] + dp1[f2] + 1);//更新答案,即把最长1链和次长1链接在一起(通俗地讲),显然魔力值为1 
		if (s1 != 0) {//判断是否含有2链(有可能没有) 
			dp2[now] = dp2[s1] + 1;//那么,更新now的最长2链,即为它的子节点中最长的2链 +1(含义同上) 
			if (s1 != f1) {//这就是为何要取次长链的原因,有可能你的最长1链和最长2链是由同一个子节点延伸的,那么该子节点就被重复取了 
				update(2 , dp1[f1] + dp2[s1] + 1);//如果不同,那么就把最长1链和最长2链接在一起(肯定优先考虑最长的,不到万不得已不用次长) 
			} else {
				//如果相同,那么启用次长链 
				update(2 , dp1[f2] + dp2[s1] + 1);//先用次长1链和最长2链更新 
				if (s2 != 0) {//再特判是否存在次长2链 
					update(2 , dp1[f1] + dp2[s2] + 1);//那么,用最长1链和次长2链更新 
				}
				//肯定不用次长1链和次长2链(最劣) 
			}
		}
	} else if (a[now] == 2) {//该节点魔力值为2
		dp2[now] = dp1[f1] + 1;//那么,只能用最长的1链来更新(要保证只能有一个2) 
		update(2 , dp1[f1] + dp1[f2] + 1);//把最长1链和次长1链接在一起,魔力值为2(当前节点的魔力值为2) 
	}
	//隐藏的代码:如果啊a[now] > 2,不列入我们的讨论范围 
}
void ggcd(ll p , ll q) {
	int G = __gcd(p , q);//取最大公约数,进行约分 
	p /= G;
	q /= G;
	printf("%d/%d", p , q);
}
int main() {
	scanf("%d", &n);
	for (int i = 1 , u , v ; i < n ; i ++) {
		scanf("%d %d", &u , &v);
		add(u , v);//双向存边,本题没有父子关系 
		add(v , u);
	}
	for (int i = 1 ; i <= n ; i ++) {
		scanf("%d", &a[i]);
		minn = min(minn , a[i]);//取最小值,用于特判 
	}
	if (minn > 1) {//特判,如果一个1都没有,则答案为节点元素的最小值 
		printf("%d/1", minn);
		return 0;
	}
	Dp(1 , 0);//进行树形DP 
	ggcd(p , q);//对p,q进行约分(但好像不需要,以防万一) 
	return 0;
}
posted @ 2024-03-26 15:51  Fracture_Dream  阅读(4)  评论(0编辑  收藏  举报  来源