题目大意:
在一棵树中 选出k个联通块 使得 这k个联通块的点权总和 / k 最大
并且这k个联通块不相互覆盖(即一个点只能属于一个联通块)
如果有多种方案,找到k最大的那种
给定n 有n个点
给定n个点的点权(点权可能出现负数)
给定这个树的n-1条边
当将所有点分成联通块后,比较各个强联通块的点权总和,绝对存在最大值,而点权总和=最大值的也可能有多个
此时 若选择了所有点权总和等于最大值的联通块,那么 /k 之后得到的 ans=这个最大值
假设继续选择次大值,那么此时 res = (ans*k+次大值 ) / (k+1) ,显然这个res会因次大值而 res<ans,即选择次大值无法使得答案更优
所以若我们找到了最大值,最优的选择就是 直接选择所有点权总和等于最大值的联通块,无论点权总和最大值为正负,都是这样
不过由于点权存在负数,若要最大化点权总和,显然不能将点权为负数的点并做联通块
所以当u点要将v点并做联通块时,应先考虑v点的权值是否小于0,若小于0则不并
#include <bits/stdc++.h> #define INF 0x3f3f3f3f #define LL long long using namespace std; const int N=3e5+5; int n,k,a[N]; LL ans,fe[N]; vector <int> E[N]; void dfs(int u,int fa,int op) { fe[u]=(LL)a[u]; for(int i=0;i<E[u].size();i++) { int v=E[u][i]; if(v==fa) continue; dfs(v,u,op); fe[u]+=max(fe[v],0LL); } if(op) ans=max(ans,fe[u]); else if(fe[u]==ans) k++, fe[u]=0LL; // 搜索最大值个数时 除记录个数外 将fe[u]置零 // 防止父亲(或父亲的父亲...)因加上该值又得到最大值 则发生覆盖 } int main() { while(~scanf("%d",&n)) { for(int i=1;i<=n;i++) { scanf("%d",&a[i]); E[i].clear(); } for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); E[u].push_back(v), E[v].push_back(u); } ans=-INF; k=0; dfs(1,0,1), dfs(1,0,0); // 1时搜出最大值 0时搜出不相互覆盖的最大值的个数 printf("%I64d %d\n",ans*(LL)k,k); } return 0; }
原文地址:https://www.cnblogs.com/zquzjx/p/10090202.html
时间: 2024-11-12 21:58:43