看一眼感觉 $dp$,发现状态没法维护
考虑贪心,然后就想了两个错的贪心...
正解好神啊
首先如果权值最大的点能够一步染色那么肯定要染它
意思就是,一旦父节点被染色那么它就要接着被染色
那么把它们父子两合并成一个新的点,其他节点根据原来的边也连上来
考虑新的点的权值要怎么搞,现在既然这个节点包含了两个点,那么把它染色要两个单位时间,而染其他点只要 $1$ 单位时间
此时染它对整颗树产生的额外的代价为 $2$ 乘其他节点权值和,把其他点 $x$ 染色额外代价为 $1$ 乘其他节点 (非 $x$ 节点) 权值和
所以权值为原本节点权值之和除以 $2$,更大的情况也是同样处理,每次合并都计算此时合并的贡献
贡献就是父节点大小乘子节点权值和,因为要把子节点染色得先把父节点染好,此时子节点会产生额外的代价
这样一直合并最后只剩下根节点时答案就出来了,具体看代码吧
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<vector> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+7; int n,rt,val[N],sz[N],fa[N],ans; inline void clr() { for(int i=1;i<=n;i++) fa[i]=val[i]=sz[i]=0; ans=0; } void solve() { for(int i=1;i<n;i++) { int x=0,p; for(int j=1;j<=n;j++) if(j!=rt && 1.0*val[x]/sz[x]<1.0*val[j]/sz[j]) x=j;//找到权值最大的点 p=fa[x]; ans+=sz[p]*val[x];//我们这里算的是额外的代价,不包括把本身染色的代价 sz[p]+=sz[x]; val[p]+=val[x]; val[x]=0; fa[x]=p;//合并 for(int j=1;j<=n;j++) if(fa[j]==x) fa[j]=p;//其他点按原来关系连上来 } } int main() { sz[0]=1; while(233) { n=read(),rt=read(); int a,b; if(!n&&!rt) break; for(int i=1;i<=n;i++) val[i]=read(),ans+=val[i],sz[i]=1;//ans初始为所有点染色本身的代价 for(int i=1;i<n;i++) { a=read(),b=read(); fa[b]=a; } solve(); printf("%d ",ans); clr(); } return 0; }