大一时,我们在某次集训队训练时,\(zjn\)学长就笑着调侃道:“树形dp在我们当年只是一道铁牌题,连树形dp都不会还打什么ACM”。于是乎,抱着一种好奇心,也为了能够在比赛时做出树形\(dp\)的题目,我便开始了树形\(dp\)的学习。
树形\(dp\),顾名思义,就是在树上\(dp\),又由于树固有的递归性质,树形\(dp\)一般都是递归进行的。
既然使用递归去实现,那么我们自然需要用到\(dfs\)函数去搜索整棵树,而对于\(dp\)数组的参数选择,一般选择当前枚举到的节点\(u\),并通过它的儿子节点\(v\)来更新,其他维记录其他信息。
而对于树形\(dp\)的基本形式,已经有大佬总结出了较为模板的做法,设\(dp[i][j][0/1]\),其中\(i\)是以\(i\)为根的子树,\(j\)表示在以\(i\)为根的树中选\(j\)个子节点,\(0\)表示当前节点不选,\(1\)表示选上当前节点,有时候可以压掉\(j\)或者0/1这维。有关基础的\(dp\)方程模板
树形\(dp\)中的换根\(dp\)问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。
通常需要两次\(DFS\),第一次\(DFS\)预处理诸如深度,点权和之类的信息,在第二次\(DFS\)开始运行换根动态规划。
通过以上的学习,我们对于树形\(dp\)有了一定的了解,接下来就做点例题来小试牛刀
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,a[6006],t=0,head[6006],f[6006][2],in[6006]={0},w; struct node { ll v,next; }e[6006]; void add(ll u,ll v) { e[++t].v=v; e[t].next=head[u]; head[u]=t; } void dp(ll x) { f[x][0]=0; f[x][1]=a[x]; for(ll i=head[x];i;i=e[i].next) { ll v=e[i].v; dp(v); f[x][0]+=max(f[v][0],f[v][1]); f[x][1]+=f[v][0]; } } signed main() { ios::sync_with_stdio(false); cin>>n; for(ll i=1;i<=n;++i)cin>>a[i]; for(ll i=1;i<n;++i) { ll u,v; cin>>u>>v; add(v,u); in[u]++; } for(ll i=1;i<=n;++i)if(in[i]==0)w=i; dp(w); cout<<max(f[w][0],f[w][1]); return 0; }
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,a[20000],head[20000],t=0,f[20000],ans=-0x7fffffff; struct node { ll v,next; }e[40000]; void add(ll u,ll v) { e[++t].v=v; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll fa) { f[u]=a[u]; for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v!=fa) { dfs(v,u); if(f[v]>0) f[u]+=f[v]; } } } signed main() { ios::sync_with_stdio(false); cin>>n; for(ll i=1;i<=n;++i)cin>>a[i]; for(ll i=1;i<n;++i) { ll u,v; cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); for(ll i=1;i<=n;++i)ans=max(f[i],ans); cout<<ans; return 0; }
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,m,head[306],t=0,a[306],f[306][306]; struct node { ll v,next; }e[306]; void add(ll u,ll v) { e[++t].v=v; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll sum) { if(sum==0)return; for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; for(ll k=0;k<sum;++k)f[v][k]=f[u][k]+a[v]; dfs(v,sum-1); for(ll k=1;k<=sum;++k)f[u][k]=max(f[u][k],f[v][k-1]); } } signed main() { ios::sync_with_stdio(false); cin>>n>>m; for(ll i=1;i<=n;++i) { ll u; cin>>u>>a[i]; add(u,i); } dfs(0,m); cout<<f[0][m]; return 0; }
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,q,head[205],t=0,f[105][105]; struct node { ll v,next,w; }e[205]; void add(ll u,ll v,ll w) { e[++t].v=v; e[t].w=w; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll fa,ll w) { ll son[3]={0},cnt=0; bool p=0; for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v!=fa) { p=1; son[++cnt]=i; dfs(v,u,e[i].w); } } if(p==0)return; for(ll i=1;i<=q;++i) { for(ll j=0;j<=i;++j) { ll w=0; if(j-1>=0)w+=e[son[1]].w; if(i-j-1>=0)w+=e[son[2]].w; if(j)f[u][i]=max(f[u][i],f[e[son[1]].v][j-1]+f[e[son[2]].v][i-j-1]+w); else f[u][i]=max(f[u][i],f[e[son[2]].v][i-j-1]+w); } } } signed main() { ios::sync_with_stdio(false); cin>>n>>q; for(ll i=1;i<=n-1;++i) { ll u,v,w; cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs(1,0,0); cout<<f[1][q]; return 0; }
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,head[2000005],t=0,f[1000005],num[1000005],sum=0; struct node { ll v,next; }e[2000005]; void add(ll u,ll v) { e[++t].v=v; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll fa,ll deep) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v==fa)continue; sum+=deep; dfs(v,u,deep+1); num[u]+=num[v]; } } void dfs2(ll u,ll fa) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v==fa)continue; f[v]=f[u]-num[v]+n-num[v]; dfs2(v,u); } } signed main() { ios::sync_with_stdio(false); cin>>n; for(ll i=1;i<=n;++i)num[i]=1; for(ll i=1;i<=n-1;++i) { ll u,v; cin>>u>>v; add(u,v); add(v,u); } dfs(1,0,1); f[1]=sum; dfs2(1,0); ll ma=-1,ans; for(ll i=1;i<=n;++i) { if(f[i]>ma) { ans=i; ma=f[i]; } } cout<<ma+n<<endl; return 0; }
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,head[2000005],t=0,f[1000005],num[1000005],sum=0; struct node { ll v,next; }e[2000005]; void add(ll u,ll v) { e[++t].v=v; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll fa,ll deep) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v==fa)continue; sum+=deep; dfs(v,u,deep+1); num[u]+=num[v]; } } void dfs2(ll u,ll fa) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v==fa)continue; f[v]=f[u]-num[v]+n-num[v]; dfs2(v,u); } } signed main() { ios::sync_with_stdio(false); cin>>n; for(ll i=1;i<=n;++i)num[i]=1; for(ll i=1;i<=n-1;++i) { ll u,v; cin>>u>>v; add(u,v); add(v,u); } dfs(1,0,1); f[1]=sum; dfs2(1,0); ll ma=-1,ans; for(ll i=1;i<=n;++i) { if(f[i]>ma) { ans=i; ma=f[i]; } } cout<<ans; return 0; }
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,a[100005],sum[100005],deep[100005]={0},head[200005],t=0,sheep=0,f[100005]; struct node { ll v,next,w; }e[200005]; void add(ll u,ll v,ll w) { e[++t].v=v; e[t].w=w; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll fa) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v,w=e[i].w; if(v==fa)continue; deep[v]=deep[u]+w; dfs(v,u); sum[u]+=sum[v]; } } void dfs2(ll u,ll fa) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v,w=e[i].w; if(v==fa)continue; f[v]=f[u]+(sheep-sum[v]-sum[v])*w; dfs2(v,u); } } signed main() { ios::sync_with_stdio(false); cin>>n; for(ll i=1;i<=n;++i){cin>>a[i];sum[i]=a[i];sheep+=a[i];} for(ll i=1;i<=n-1;++i) { ll u,v,w; cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs(1,0); for(ll i=1;i<=n;++i)f[1]+=a[i]*deep[i]; dfs2(1,0); ll ans=0x7fffffffffffffff; for(ll i=1;i<=n;++i)ans=min(ans,f[i]); cout<<ans<<endl; return 0; }
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,head[800005],t=0,f[400004],sec[400004],mas[400004],ma[400004],siz[400004],g[400004]; struct node { ll v,next; }e[800005]; void add(ll u,ll v) { e[++t].v=v; e[t].next=head[u]; head[u]=t; } void dfs(ll u,ll fa) { ma[u]=0; for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v==fa)continue; dfs(v,u); siz[u]+=siz[v]; if(siz[v]>siz[ma[u]])ma[u]=v; if(siz[v]>n/2) { if(f[v]>f[u]) { sec[u]=f[u]; f[u]=f[v]; mas[u]=v; } else if(f[v]>sec[u])sec[u]=f[v]; } else { if(siz[v]>f[u]) { sec[u]=f[u]; f[u]=siz[v]; mas[u]=v; } else if(siz[v]>sec[u])sec[u]=siz[v]; } } } void dfs2(ll u,ll fa) { for(ll i=head[u];i;i=e[i].next) { ll v=e[i].v; if(v==fa)continue; g[v]=max(g[v],g[u]); if(n-siz[v]<=n/2)g[v]=max(g[v],n-siz[v]); if(v==mas[u])g[v]=max(g[v],sec[u]); else g[v]=max(g[v],f[u]); dfs2(v,u); } } signed main() { ios::sync_with_stdio(false); cin>>n; for(ll i=1;i<=n-1;++i) { ll u,v; cin>>u>>v; add(u,v); add(v,u); } for(ll i=1;i<=n;++i)siz[i]=1; dfs(1ll,0ll); g[1]=0; dfs2(1ll,0ll); for(ll i=1;i<=n;++i) { if(siz[ma[i]]>n/2&&siz[ma[i]]-f[ma[i]]>n/2)cout<<0<<" "; else if(n-siz[i]>n/2&&n-siz[i]-g[i]>n/2)cout<<0<<" "; else cout<<1<<" "; } return 0; }
另外还要考虑边界条件,若\(i\)为叶子,则\(f[i]=a[i]\)
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll n,a[200005],f[200005],ans=-0x7fffffffffffffff; vector<pair<ll,ll>>p[200005]; void dfs(ll u,ll fa) { for(auto y:p[u]) { if(y.first==fa)continue; dfs(y.first,u); if(f[y.first]+y.second>0)f[u]+=f[y.first]+y.second; } ans=max(ans,f[u]); } signed main() { ios::sync_with_stdio(false); cin>>n; ll now=n; for(ll i=1;i<=n;++i)cin>>a[i]; for(ll i=1;i<=n-1;++i) { ll u,v,w; cin>>u>>v>>w; p[u].push_back({v,w}); p[v].push_back({u,w}); } for(ll i=1;i<=now;++i)f[i]=a[i]; dfs(1,0); cout<<ans; return 0; }
void build(ll u,ll x) { son[u]=x; for(ll i=1;i<=x;++i) { ++now; cin>>c; add(u,now); build(now,c-'0'); } }
for(int k=1;k<=总共的组数;k++)//遍历所有的组k for(int j=v;j>=1;j--)//跟01背包类似,倒序枚举背包容量 for(int i=1;i<=组中的元素个数;i++)//遍历这组中所有的元素
void dfs(int now,int p) { dp[now][0]=1; for (int i=0;i<v[now].size();i++) { int to=v[now][i]; if (to!=p) { dfs(to,now); for (int j=0;j<k;j++) ans+=(dp[now][j]*dp[to][k-j-1]); for (int j=0;j<k;j++) dp[now][j+1]+=dp[to][j]; } } }
树形\(dp\)大多数对根和儿子进行转移,对于树上背包问题,前提要对简单的背包问题足够了解,也要注意枚举时的顺序,而换根\(dp\)无非就是对根进行转移,运用两次\(dfs\),第一次预处理,第二次转移。
完结撒花qwq