比赛的时候看到题意没多想就放弃了。结果最后D也没做出来,还掉分了,所以还是题目做的太少,人太菜。
回到正题:
题意:一棵树,点带权值,然后求k个子连通块,使得k个连通块内所有的点权值相加作为分子除以k的值最大,如果这样的最大值有多个,就最大化k。
赛后看了看别人的代码仔细想了一想,还是挺容易的。
首先将树分为若干个连通块,考虑一个权值求和最大的连通块,设该最大值为sum,那么其他所有的连通块,权值求和都不可能比当前的连通块大。
在不考虑最大化k的情况下,就没有必要再将其他的连通块加进来(因为如果加进来,(sum+加进来的连通块权值和)/(k+1)<=sum/k,就必定成立,其实本质上就是求一个均值,加比当前值还要小的值会使得这个均值变小),所以答案就是sum。
现在要考虑答案相同时,最大化k,那么本质上只要将其他的权值和等同于当前最大权值和的连通块加进来就好,这样分子就变成了sum*k,k个连通块。
经队友赛后一提醒,其实实际上这就是一个最大连续和的树上版本,树形dp一下就出来了。
设dp[i]表示以i为根的子树,包含i在内的权值求和最大的连通块的权值和。
状态转移就有两种选择,枚举他的子树,那么:1.连这棵子树;2.不连这颗子树。
易得状态转移方程:
dp[i]+=max(dp[j],0) (j为i的子节点)(类似于线性结构上的最大连续和)
然后获取所有dp中的最大值,就是sum。
最后再遍历一遍dp数组,每有一个dp值与sum相同,就k++,最后答案就是sum*k,k个连通块。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 const int maxn=300005; 5 int cnt; 6 int head[maxn]; 7 struct edge 8 { 9 int to,nxt; 10 }e[maxn]; 11 ll a[maxn]; 12 void inline addedge(int u,int v) 13 { 14 e[++cnt].to=v; 15 e[cnt].nxt=head[u]; 16 head[u]=cnt; 17 } 18 ll dp[maxn]; 19 ll ans=-1e18; 20 void dfs(int fa,int u) 21 { 22 dp[u]=a[u]; 23 for(int i=head[u];i;i=e[i].nxt) 24 { 25 int v=e[i].to; 26 if(v==fa) continue; 27 dfs(u,v); 28 dp[u]+=max(dp[v],0LL); 29 } 30 ans=max(ans,dp[u]); 31 } 32 int main() 33 { 34 #ifdef local 35 //freopen("in.txt","r",stdin); 36 #endif // local 37 int n; 38 cin>>n; 39 for(int i=1;i<=n;i++) 40 cin>>a[i]; 41 for(int i=1;i<=n-1;i++) 42 { 43 int x,y; 44 cin>>x>>y; 45 addedge(x,y); 46 addedge(y,x); 47 } 48 dfs(-1,1); 49 ll k=0; 50 for(int i=1;i<=n;i++) 51 if(dp[i]==ans) 52 k++; 53 cout<<ans*k<<" "<<k<<endl; 54 }
原文地址:https://www.cnblogs.com/iamamori/p/10068831.html
时间: 2024-10-01 00:31:51