树形动态规划、二次扫描

使用 $a_i$ 表示节点 $i$ 的点权,将黑点的点权改为 $-1$,问题转化为对每个 $i$ 求树上包含 $i$ 的连通块的点权和的最大值。

指定 $1$ 为根节点,设计 $dp_i$ 为节点 $i$ 子树中包含 $i$ 的连通块的点权和的最大值,枚举子节点转移。

$$dp_i = a_i + \sum\limits_{j \in \operatorname{son}(i)} \max(dp_j,0)$$

通过第一次遍历可以得到节点 $1$ 的答案,接下来变更状态信息,$dp_i$ 表示树上包含节点 $i$ 的连通块的点权和的最大值,二次扫描更新 $dp_i$ 的值得到答案。

时间复杂度 $O(n)$。

#include<bits/stdc++.h>
using namespace std;
int n,a[200'005],dp[200'005],ans[200'005];
vector<int> v[200'005];
void dfs(int x,int las){
    dp[x]=a[x];
    for(int i:v[x]) if(i!=las){
        dfs(i,x);
        dp[x]+=max(dp[i],0);
    }
}
void tree(int x,int las){
    ans[x]=dp[x];
    for(int i:v[x]) if(i!=las){
        dp[x]-=max(dp[i],0);
        dp[i]+=max(dp[x],0);
        tree(i,x);
        dp[i]-=max(dp[x],0);
        dp[x]+=max(dp[i],0);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) if(a[i]==0) a[i]=-1;
    for(int i=1;i<=n-1;i++){
        static int x,y;
        scanf("%d%d",&x,&y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs(1,0),tree(1,0);
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    return 0;
}
最后修改:2024 年 10 月 11 日
如果觉得我的文章对你有用,请随意赞赏