[总结] 树形 dp
树形dp
概念类
树形dp是一种很优美的动态规划,真的很优美真的,前提是在你学会它之后。
实现形式
树形dp的主要实现形式是dfs,在dfs中dp,主要的实现形式是dp[ i ][ j ][ 0/1 ],i是以i为根的子树,j是表示在以i为根的子树中选择j个子节点,0表示这个节点不选,1表示选择这个节点。有的时候j或0/1这一维可以压掉
基本的dp方程
1.选择节点类
dp[i][0]=dp[j][1]dp[i][1]=max/min(dp[j][0],dp[j][1])
2.树形背包类
{dp[v][k]=dp[u][k]+valdp[u][k]=max(dp[u][k],dp[v][k−1])
例题类
以上就是对树形dp的基本介绍,因为树形dp没有基本的形式,然后其也没有固定的做法,一般一种题目有一种做法。
没有上司的舞会传送门
这道题是一树形dp入门级别的题目,具体方程就用到了上述的选择方程。
#include <iostream>
#include <algorithm>
#include <cstdio>
#define re register
using namespace std;
const int maxn=6005;
int head[maxn],cnt=0,n,f[maxn][2],a[maxn],ans=0,vis[maxn];
struct tree{
int to,nxt;
}e[maxn];
inline void link(int u,int v){
e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
inline void solve(int u){
f[u][1]=a[u];f[u][0]=0;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
solve(v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
}
}
int main(){
scanf("%d",&n);
for(re int i=1;i<=n;i++)scanf("%d",a+i);
for(re int i=1;i<=n-1;i++){
int u,v;
scanf("%d%d",&u,&v);
link(v,u);
vis[u]=true;
}
int root;
for(re int i=1;i<=n;i++)if(!vis[i]){root=i;break;}
solve(root);
cout<<max(f[root][1],f[root][0]);
return 0;
}
最大子树和
这道题的dp方程有变,因为你的操作是切掉这个点,所以你的子树要么加上价值,要么价值为0,所以dp方程是
dp[u]+=max(dp[v],0)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct edge
{
int next,to;
} e[40000];
int head[40000],tot,rt,maxn;
void add(int x,int y)
{
e[++tot].next=head[x];
head[x]=tot;
e[tot].to=y;
}
int n,dp[20000],ind[20000];
int val[20000],f[20000];
void dfs_f__k(int x,int fa)
{
f[x]=fa;
for(int i=head[x]; i; i=e[i].next)
{
int v=e[i].to;
if(v!=fa)
dfs_f__k(v,x);
}
}
void dfs(int x)
{
dp[x]=val[x];
for(int i=head[x]; i; i=e[i].next)
{
int v=e[i].to;
if(v!=f[x])
{
dfs(v);
dp[x]+=max(0,dp[v]);
}
}
maxn=max(maxn,dp[x]);
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)scanf("%d",&val[i]);
for(int i=1; i<=n-1; i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
rt=1;
dfs_f__k(rt,0);
dfs(rt);
printf("%d",maxn);
}
选课P2014
这道题的意思是每本书要想选择一门课,必须要先学会它的必修课,所以这就形成了一种依赖行为,即选择一门课必须要选择必修课。那么他又说要选择的价值最大,这就要用到树形背包的知识了。
树形背包的基本代码形式(即上面的树形背包类)
/*
设dp[i][j]表示选择以i为根的子树中j个节点。
u代表当前根节点,tot代表其选择的节点的总额。
*/
void dfs(int u,int tot)
{
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
for(int k=0;k<tot;k++)//这里k从o开始到tot-1,因为v的子树可以选择的节点是u的子树的节点数减一
dp[v][k]=dp[u][k]+val[u];
dfs(v,tot-1)
for(int k=1;k<=tot;k++)
dp[u][k]=max(dp[u][k],dp[v][k-1]);//这里是把子树的值赋给了根节点,因为u选择k个点v只能选择k-1个点。
}
}
然后这就是树形背包的基本形式,基本就是这样做
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=305,maxm=305;
int head[maxn],w[maxn],f[maxn][maxn],cnt = 0,n,m;
int sz[maxn];
struct Tree{
int to,nxt;
}e[maxm];
inline void link(int u,int v){
e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
void dp(int x){
sz[x]=1;
f[x][0]=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
dp(y);
sz[x]+=sz[y];
for(int t=min(sz[x],m);t>=0;t--){//选课门数(容量),倒序循环来正确处理体积为 0 的物品
for(int j=0;j<=min(t,sz[y]);j++){
f[x][j]+=f[y][0];
if(t-j>=0 && f[x][t-j]!=-1)
f[x][t]=max(f[x][t],f[y][j]+f[x][t-j]);
}
}
}
if(x!=0){
for(int t=m;t>0;t--)f[x][t]=f[x][t-1]+w[x];//最后必须覆盖掉 ,自己占一份体积
}
}
int main(){
scanf("%d%d",&n,&m);
memset(f,-1,sizeof f);
for(int i=1;i<=n;i++){
int pre;
scanf("%d%d",&pre,w+i);
if(pre)link(pre,i);
else link(0,i);
}
dp(0);
printf("%d",f[0][m]);
return 0;
}
Strategic game
这道题的意思是选择最少的点来覆盖一棵树,可以用最小点覆盖(也就是二分图最大匹配)或者树形dp来做,因为这里我们的专题是树形dp,所以我们现在就讲树形dp的做法。
我们做这道题的方法是用选择方程来做,因为你要做最小点覆盖,要么选这个点要么不选对吧。
于是dp的转移方程就是上述一方程
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int n;
struct edge
{
int next,to;
} e[4000];
int head[4000],tot,dp[4000][2],ind[4000];
void add(int x,int y)
{
e[++tot].next=head[x];
head[x]=tot;
e[tot].to=y;
}
void dfs(int x)
{
dp[x][1]=1;
for(int i=head[x]; i; i=e[i].next)
{
int v=e[i].to;
dfs(v);
dp[x][0]+=dp[v][1];
dp[x][1]+=min(dp[v][0],dp[v][1]);
}
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(dp,0,sizeof(dp));
memset(head,0,sizeof(head));
memset(ind,0,sizeof(ind));
tot=0;
for(int j=1; j<=n; j++)
{
int a,b;
scanf("%d:(%d)",&a,&b);
for(int i=1; i<=b; i++)
{
int c;
scanf("%d",&c);
ind[c]++;
add(a,c);
}
}
int rt;
for(int i=0; i<=n; i++)
if(!ind[i])
{
rt=i;
break;
}
dfs(rt);
printf("%d\n",min(dp[rt][1],dp[rt][0]));
}
}