洛谷 P6287 [COCI2016-2017#1] Mag
题目描述
你将获得一棵由无向边连接的树。树上每个节点都有一个魔力值。
我们定义,一条路径的魔力值为路径上所有节点魔力值的乘积除以路径上的节点数。
例如,若一条路径包含两个魔力值分别为 3,53,5 的节点,则这条路径的魔力值为 3 imes 5/2=7.53×5/2=7.5。
请你计算,这棵树上魔力值最小的路径的魔力值。
输入格式
第一行一个整数 nn,表示树共有 nn 个节点,编号为 1dots n1…n。
接下来 n-1n−1 行,每行两个整数 a_i,b_ia**i,b**i,表示编号为 a_i,b_ia**i,b**i 的两个节点由一条无向边连接。
接下来 nn 行,每行一个整数 x_ix**i,表示编号为 ii 的节点的魔力值。
输出格式
一行,一个既约分数 p/qp/q。
题解:
2020.11.23模拟赛T4 25分场
觉得可以用点分治做。但是考场上一直没写出来。如果觉得点分治做法可行的小伙伴可以私信或留言联系我。咱俩一起研究。
在考场上发现一个性质:1越多贡献越多。而且,除了1之外,再往里加任何数都不优。
于是树形DP求了个最长的1链。最后假了,挂了好多分。
问题出在哪呢?
原来,2也是可以被放进这条链的。可以这样去想:现在有两条最长1链(分开的),只需要中间的一个2就可以把它们沟通起来。这样的答案有可能更优秀。
于是我们就可以用(f[i],g[i])分别统计以i为根的子树中,以i为一端且魔力值为1的最大长度、魔力值为2的最大长度。
当然,根据之前的思路,我们为了统计这些信息,还需要统计最大值、次大值。
具体的在代码中实现:
#include<cstdio>
using namespace std;
const int maxn=1e6+6;
struct Node
{
int next;
int to;
}edge[maxn<<1];
int head[maxn],cnt;
void add(int u,int v)
{
edge[cnt].next=head[u];
edge[cnt].to=v;
head[u]=cnt++;
}
int mag[maxn];
int f[maxn],g[maxn];
int p=1e9,q=1,mn=1e9;
void update(int x,int y)
{
if(p*y>q*x)
p=x,q=y;
return;
}
void dfs(int x,int fa)
{
int u1=0,u2=0,v1=0,v2=0;
for(int i=head[x];~i;i=edge[i].next)
{
int t=edge[i].to;
if(t!=fa)
{
dfs(t,x);
if(f[t]>f[u1]){
u2=u1,u1=t;
}
else if(f[t]>f[u2]){
u2=t;
}
if(g[t]>g[v1]){
v2=v1,v1=t;
}
else if(g[t]>g[v2]){
v2=t;
}
}
}
if(mag[x]==1)
{
f[x]=f[u1]+1;
update(1,f[u1]+f[u2]+1);
if(v1!=0)
{
g[x]=g[v1]+1;
if(u1!=v1)
update(2,f[u1]+g[v1]+1);
else
{
update(2,f[u2]+g[v1]+1);
if(v2!=0)
update(2,f[u1]+g[v2]+1);
}
}
}
else if(mag[x]==2)
{
g[x]=f[u1]+1;
update(2,f[u1]+f[u2]+1);
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i)
head[i]=-1;
for(int i=1;i<n;++i)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;++i)
{
scanf("%d",mag+i);
if(mag[i]<mn)
mn=mag[i];
}
if(mn>1)
{
printf("%d/1",mn);
return 0;
}
dfs(1,0);
printf("%d/%d",p,q);
return 0;
}