
比赛的时候看到题意没多想就放弃了。结果最后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。
之后发现一个问题,如何区别以下情况:
存在多个连通块权值和最大,但其中某个连通块是另一个连通块的子图。
比方说以下这组数据:
4
0 1 1 1
1 2
2 3
2 4
如果只是朴素计数(计算有多少个结点的dp值等于sum),那么答案将会是2(dp[1]==dp[2]==ans==3),而实际答案应该是1,这是因为以2为根的子树被包含在以1为根的子树中。
为了解决这个问题,考虑再做一次树形dp,每次dfs退出该结点时如果当前结点的dp值等于sum,就k++,然后dp值改为0,就将该结点与父节点断开了,最后答案就是sum*k,k个连通块。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=;
int cnt;
int head[maxn];
struct edge
{
int to,nxt;
}e[*maxn];
ll a[maxn];
void inline addedge(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
ll dp[maxn];
ll ans=-1e18,k;
void dfs(int fa,int u)
{
dp[u]=a[u];
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(u,v);
dp[u]+=max(dp[v],0LL);
}
ans=max(ans,dp[u]);
}
void dfs2(int fa,int u)
{
dp[u]=a[u];
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs2(u,v);
dp[u]+=max(dp[v],0LL);
}
if(dp[u]==ans)
{
k++;
dp[u]=;
}
}
int main()
{
#ifdef local
//freopen("in.txt","r",stdin);
#endif // local
ios::sync_with_stdio(false);
int n;
cin>>n;
for(int i=;i<=n;i++)
cin>>a[i];
for(int i=;i<=n-;i++)
{
int x,y;
cin>>x>>y;
addedge(x,y);
addedge(y,x);
}
dfs(-,);
dfs2(-,);
cout<<ans*k<<" "<<k<<endl;
}