【16年浙江省赛 B ZOJ 3937】More Health Points 题解
作者:互联网
题目链接
\(Describe\)
给定一棵以节点\(1\)为根的树,节点间单向边连接,每个节点有权值\(B_i\),找到其中一串子链(也可以为空),该子链最靠近\(1\)的节点为该子链的根节点,计算每个节点的深度(根节点深度为\(1\))与权值的乘积,并求和,即\(ans= \sum\limits_{i=1}^ndeep_i*B_i\)(其中\(n\)表示该子链节点数,\(deep_i\)表示第\(i\)个节点的深度),输出最大的和(若所选子链为空,和为\(0\))
\(Solution\)
这道题是大一时我在校集训队\(vp\)训练时做的一道题,当时只有我们队出了这题,而我是负责这题的,也正是托这题的福,我们成功拿下了当时榜单\(rank1\),超过了大二大三的学长(雾)。
赛后我去网上搜了一下这个题,结果呢,好家伙,居然这么难?好多博客的题解有斜率优化\(dp\)、动态维护下凸壳、凸包凸壳等我不会的算法,当时一脸懵逼,因为在\(vp\)时,我根本没有想这么多,我就单纯树形\(dp\)跑\(dfs\)就\(AC\)了这题。
具体讲讲我的做法,这题我的第一想法就是跑\(dfs\),最开始我是从根节点\(1\)开始\(dfs\),但是\(TLE\)了,算法也会变得相当复杂。于是,我换了个思路,反向建边,从叶子节点跑\(dfs\)到根节点,这样对于答案求和的计算也会非常简单,用\(sum[i]\)表示从叶子节点到\(i\)节点的\(B_i\)树上前缀和,\(dp[i]\)表示跑到\(i\)节点的求和答案值,不难推出状态转移方程
\(dp[v]=dp[u]+sum[v]\)
\(dp[v]=max(dp[v],B[v])\)
另外,要一边跑\(dfs\)一边维护前缀和\(sum\)的值。但是,这样子去写会\(TLE\),于是我开始了剪枝优化,对于一些已经跑过了的点,可以省略不跑,用\(vis\)数组记录跑过的点,如果满足
\(vis[v]=1,dp[u]+a[v]+sum[u]\le dp[v],sum[u]+a[v]\le sum[v]\)
就可以停止\(dfs\)了,直到把所有的叶子节点跑完,答案输出为\(dp\)数组的最大值,最小输出\(0\).因为对于\(n\)个节点的树,其叶子节点最多\((n+1)/2\)个,所以不剪枝优化时,时间复杂度是\(O(t*nlogn)\)会\(TLE\),但是优化后其复杂度降了下来,于是乎就\(AC\)了这题。(其实数据还是比较水的,加强点可能会\(T\))
\(Code\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,a[100005],b,head[100005],tot,ans,dp[100005],sum[100005],in[100005],vis[100005];
struct node
{
ll v,next;
}e[100005];//链式前向星存图
void add(ll u,ll v)
{
e[++tot].v=v;
e[tot].next=head[u];
head[u]=tot;
}//加边操作
void dfs(ll u)
{
vis[u]=1;
if(head[u]){ll v=e[head[u]].v;//因为从叶子节点出发能到的点最多只有1个,所以相当于一条链,不需要for循环
if(vis[v]==1&&dp[u]+a[v]+sum[u]<=dp[v]&&sum[u]+a[v]<=sum[v])return;//此解法的关键,剪枝优化,否则TLE
else
{
sum[v]=a[v]+sum[u],dp[v]=dp[u]+sum[v],ans=max(ans,max(dp[v],a[v]));//状态转移,记录最大答案
if(dp[v]>a[v])dfs(v);
else
{
dp[v]=a[v];
sum[v]=a[v];
dfs(v);
}
}
}
}
signed main()
{
scanf("%lld",&t);
while(t--)
{
memset(head,0,sizeof head);
memset(in,0,sizeof in);
memset(vis,0,sizeof vis);
memset(dp,0x80,sizeof dp);
ans=tot=0;
scanf("%lld",&n);
for(ll i=1;i<=n;++i)scanf("%lld",&a[i]);
for(ll i=2;i<=n;++i)
{
scanf("%lld",&b);
add(i,b);//反向建边
in[b]++;//有点类似拓扑排序,其实就是记录叶子节点
}
for(ll i=1;i<=n;++i)if(in[i]==0)
{
sum[i]=a[i];
dp[i]=a[i];
dfs(i);
}
printf("%lld\n",ans);
}
return 0;
}
看了其他解法的题解代码都比较长,有上百行,我的代码只有\(60\)行\((qwq)\)
标签:ll,16,题解,sum,dfs,节点,100005,ZOJ,dp 来源: https://www.cnblogs.com/tomoyo13/p/16100197.html