首页 > 其他 > 详细

洛谷p1122 Tree DP

时间:2019-07-28 18:42:51      阅读:47      评论:0      收藏:0      [点我收藏+]

题意:给定一颗无根树,每个点有一个点权,可以有负数。求一个联通块,使得这个联通块内的点权之和最大。

分析:我们看到这道题,第一反应是贪心。把每个点当做根跑一次贪心,在根的子树中,如果一个子树的权值和大于0,就把子树的权值加上,否则就剪去这个子树。这样从根节点一层一层递归下来,先处理点的子树,再处理点本身,我们就得到了以某个节点为根的最大权值和。每个点都跑一次,取一个最大值,就得到了答案。这样做一定是对的,但是复杂度O(n^2),加一个记忆化搜索能过。

  但今天要讲的是Tree DP的思路。随便选一个点作为根,记f[i]为以i为根的子树的最大权值和,跑一次树上的DP就行。虽然有递归,但每个点只会经过一次,复杂度O(n)。

至于为什么以任意一个点作为根都行,请看下面的分析:
技术分享图片

假设我们以节点1为根算出了一个最大联通块和,此时的联通块中一定包含节点1的权值。如果以2为根算最大权值,那分为两种情况:如果没有节点2这一部分,f[1]都大于0的话,那么节点2做DP时一定会把1那部分加上,结果就等效为从节点1做DP。如果没有节点2这一部分,f[1]小于0的话,说明节点1的权值小于0,那么2肯定不会选1这部分,程序中很重要的一句ans=max(ans,f[u]);就发挥了作用,它记录了以2为根的子树的答案,相当于以2为根不选1那部分的答案,还是等效的。所以,以2为根的话,无论哪种情况,都是和以1为根的情况是等效的,其他点同理。所以,我们只要任选一个点为根就可以了。

 

#include<bits/stdc++.h>
using namespace std;
int n,ans,ecnt;
int a[16005];
bool vis[16005];
int head[16005];
int f[16005];
struct aaa
{
    int to,nxt;
}bian[32100];
inline int read()
{
    int x=0;bool f=0;char c=getchar();
    while(c<0||c>9){if(c==-)f=1;c=getchar();}
    while(c>=0&&c<=9){x=(x<<3)+(x<<1)+c-0;c=getchar();}
    return f?-x:x;
}
inline void add(int a,int b)
{
    bian[++ecnt].to=b;
    bian[ecnt].nxt=head[a];
    head[a]=ecnt;
}
void dfs(int u)
{
    vis[u]=1;f[u]=a[u];
    for(int i=head[u];i;i=bian[i].nxt)
    {
        int v=bian[i].to;
        if(!vis[v])
        {
            dfs(v);
            f[u]+=max(0,f[v]);
        }
    }
    ans=max(ans,f[u]);
}
int main()
{
    n=read();
    int aa,bb;
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n-1;i++)
    {
        aa=read();bb=read();
        add(aa,bb);add(bb,aa);
    }
    dfs(1);
    cout<<ans;
    return 0;
}

 

洛谷p1122 Tree DP

原文:https://www.cnblogs.com/oierjzy/p/11260140.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!