【树dp 结论题】Mag 6.22测试COCI


(30%的那个数据 “不会有节点和超过 2 个其他节点直接相连” 意思没怎么看明白 不影响做题(小声BB

样例:

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

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

思路:
1.首先 有个结论:
乘法是越滚越大的
只有两种链上才会产生最小的魔法值
一种是一条链上全部都是1
另一种是这一条1上有且只有一个2
(而且2在中间 否则取1较多的那一边就可以 当然 这个在树dp里是不需要我们讨论的
如果没有1和2的话,就是最小的那单独一个点(比如说样例1)

怎么证明呢
首先 全是1的那种情况当然不用说

然后
由于我太弱了 不会证明 所以搬了一个大佬的证明
觉得很有道理 就是不知道为什么要取这两种极端情况
在这里插入图片描述

(由于没有当面征求他的同意 所以可能会侵犯他的智力成果权 所以侵删)


Update 2019.6.26
嗯 我自己大概也想了一下 太多了留坑慢慢搬

2.然后就是树dp的部分啦
f[i] 从i点向下延伸能够得到的最长的全1链
g[i]:从i点向下延伸能的最长的有一个为2的链
可以类比一下求树的直径
在一个点时 这条路径可以是最长的全1链和次长的全1链拼在一起的,也有可能是最长的全1链和最长的含2 链拼在一起的
最长的全1链和最长的含2链有可能会重合 也就是都是从同一个子树上搞过来的 所以这里要特判一下

具体转移见注释:

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define MAXN 1000005
#define LL long long
int n;
//f[i] 从i点向下延伸能够得到的最长的全1链 g[i]:从i点向下延伸能的最长的有一个为2的链
int f[MAXN],g[MAXN],val[MAXN];
int ma=1000000005,mb=1;
vector<int>G[MAXN];
int rd()
{
	int f=1,x=0;char c=getchar();
	while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
	while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return f*x;
}
int gcd(int x,int y)
{
	if(y==0) return x;
	return gcd(y,x%y);
}
void upd(int x,int y)
{
	if((double)x/y<(double)ma/mb)
		ma=x,mb=y;
}
void dfs(int u,int p)
{
	int n1=0/*1的最长链*/,n2=0/*1的次长链*/,n3=0/*含2的最长链*/;
	//以上均在u的儿子之中 
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v!=p)
		{
			dfs(v,u);
			if(val[u]==1)//状态转移 
				f[u]=max(f[u],f[v]+1),g[u]=max(g[u],g[v]+1);
			if(val[u]==2)
				g[u]=max(g[u],f[v]+1/*自己就已经是2*/);
			if(f[n1]<f[v]) n2=n1,n1=v;
			else if(f[n2]<f[v]) n2=v;//没有最长链长 但是比次长链长 
			if(g[n3]<g[v]) n3=v;
		}
	}
	if(val[u]==1)
	{
		if(n1==n3)//重合 但是为什么不存含2的次长链呢? 
			upd(2,g[n3]+f[n2]+1);
		else upd(2,g[n3]+f[n1]+1);
		upd(1,f[n1]+f[n2]+1);
		f[u]=max(f[u],1);//初始化边界 
		g[u]=max(g[u],1);
	}
	if(val[u]==2)
	{
		upd(2,f[n1]+f[n2]+1);
		g[u]=max(g[u],1);
	}
}
int main()
{
	//freopen("mag.in","r",stdin);
	//freopen("mag.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++)
	{
		int u,v;
		u=rd(),v=rd();
		G[u].push_back(v);
		G[v].push_back(u);
	}
	for(int i=1;i<=n;i++)
	{
		val[i]=rd();
		ma=min(ma,val[i]);
	}
	if(ma!=1&&ma!=2)
	{
		printf("%d/1",ma);
		return 0;
	}
	dfs(1,0);
	int d=gcd(ma,mb);
	printf("%d/%d",ma/d,mb/d);
    return 0;
}

由于还有一些证明细节自己也没有搞懂
所以
To be continue…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值