题目:传送门
题意:给一棵树,每个结点有两种权值 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; }