cf 1324F Maximum White Subtree(树形dp)

题目:传送门

题意:给一棵树,每个结点有两种权值 1,-1 ;对于每一个结点,求包含它的最大连通集(权值和最大)

思路:自底向上求出,每个结点的子树方面最大连通集,然后再自顶向下(换根)求出补树(整棵树除掉该结点及其所有子树的树)方面 包含 该结点的父亲 的最大联通集;父亲的最优解-儿子对父亲的贡献,再由儿子转为父亲,父亲转为儿子,让之前的儿子加上之前的父亲的贡献。

#include<bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<double,double> pdd;
const int N=2e5+5;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const double eps=1e-9;
const long double pi=acos(-1.0L);
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define pb push_back
#define mk make_pair
#define mem(a,b) memset(a,b,sizeof(a))
LL read()
{
    LL x=0,t=1;
    char ch;
    while(!isdigit(ch=getchar())) if(ch=='-') t=-1;
    while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); }
    return x*t;
}
vector<int> e[N];
int dp[N],ans[N];//dp[i] 表示以i为根的子树,且包含i的最大连通集,显然dp[1]=ans[1]的;
void dfs(int u,int pre)//自底向上,求子树
{
    for(auto v:e[u])
    {
        if(v==pre) continue;
        dfs(v,u);
        dp[u]+=max(0,dp[v]);
    }
}//若dp[v]<0 ,那么v对u贡献为0;
void dfs2(int u,int pre,int sum)//自顶向下,求补树 //sum 表示补树中包含pre的最大连通集
{
    ans[u]=dp[u]+sum;//子树最大连通集和补数最大连通集合并(儿子和父亲是挨在一起的)
    for(auto v:e[u])
        if(v!=pre) dfs2(v,u,max(0,ans[u]-max(dp[v],0)) );//相当于换根,让父亲的最优解减去 儿子对父亲的贡献,将树分割开。儿子作根后,再加上父亲这一部分的最大连通集。
}
//更加直观的写法
void dfs2(int u,int pre)
{
    ans[u]=dp[u];
    for(auto v:e[u])
    {
        if(v==pre) continue;
        dp[u]-=max(0,dp[v]);
        dp[v]+=max(0,dp[u]);//递归换根
        dfs2(v,u);
        dp[v]-=max(0,dp[u]);//回溯还原
        dp[u]+=max(0,dp[v]);
    }
}
int main()
{
    int n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        dp[i]=x==1?1:-1;
    }
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].pb(y);
        e[y].pb(x);
    }
    dfs(1,0);
    dfs2(1,0,0);
    for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
    return 0;
}
AC代码

猜你喜欢

转载自www.cnblogs.com/DeepJay/p/12590142.html