最近做了很多树形DP的题呼,来总结一下.
希望以此增加全世界人民对树形DP的了解.
大概就是3种题型:
树上取点集的最值
每一个节点会影响与他相关的节点,所以说决策就很简单了,这个节点选或不选.
在选或不选的情况下,再加上对相关节点的影响.
状态转移方程如下:
dp[x][0]+=max(dp[y][0],dp[y][1]);
dp[x][1]+=dp[y][0];
在转移之前,先要向下递归他的儿子.
P1352的代码:
#include<bits/stdc++.h>
using namespace std;
vector<int>fas[10010];
int n,root;
int happy[10010],dp[10010][2];
bool head[10010];
void dg(int);
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",happy+i);
}
for(int i=1;i<=n-1;i++)
{
int father,son;
scanf("%d%d",&son,&father);
fas[father].push_back(son);
head[son]=true;
}
int a,b;
scanf("%d%d",&a,&b);
for(int i=1;i<=n;i++)
{
if(head[i]==false)
{
root=i;
break;
}
}
dg(root);
printf("%d",max(dp[root][1],dp[root][0]));
return 0;
}
void dg(int x)
{
dp[x][0]=0;
dp[x][1]=happy[x];
for(int i=0;i<fas[x].size();i++)
{
int y=fas[x][i];
dg(y);
dp[x][0]+=max(dp[y][0],dp[y][1]);
dp[x][1]+=dp[y][0];
}
}
树上背包
代表题P2015 二叉苹果树
P2014也很经典,只不过我是用多叉转二叉来写的.
其实便是加上的点的数量限制的上一种题型.
上一种是直接做出抉择就好了,这一种就要加上一个背包,来做出当前节点下能用最大背包容量获得的最值.
背包的过程如下:
for(int j=q;j>=1;j--)
for(int k=j-1;k>=0;k--)
dp[root][j]=max(dp[root][j],apple[root][to]+dp[root][j-k-1]+dp[to][k]);
P2015的代码:
#include<bits/stdc++.h>
using namespace std;
vector<int>tree[110];
int n,q;
int apple[110][110],dp[110][110];
bool falg[110];
void dfs(int);
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n-1;i++)
{
int a,b,val;
scanf("%d%d%d",&a,&b,&val);
tree[a].push_back(b);
tree[b].push_back(a);
apple[a][b]=val;
apple[b][a]=val;
}
dfs(1);
printf("%d",dp[1][q]);
return 0;
}
void dfs(int root)
{
falg[root]=true;
for(int i=0;i<tree[root].size();i++)
{
int to=tree[root][i];
if(falg[to]==true)continue;
falg[to]=true;
dfs(to);
for(int j=q;j>=1;j--)
for(int k=j-1;k>=0;k--)
dp[root][j]=max(dp[root][j],apple[root][to]+dp[root][j-k-1]+dp[to][k]);
}
}
子树的最值
代表题P1122 最大子树和
这类题一般是没有固定的根节点的,因为可以子树可以以任意一个节点作为根.
而且儿子与父亲的界限一般不明确(因为给出的是连接节点的边),要用前向星双向建边.
加个标记防止往回走罢.
DP数组代表着以当前节点为根的子树的最值.
DP数组附初值为节点的权值.
每一个节点向下递归,然后回溯时该节点累加上子节点.
子节点的DP值小于0(求最大值)或者大于0(求最小值)的话就不累加了.
P1122的代码:
#include<bits/stdc++.h>
using namespace std;
vector<int>tree[16010];
int n,ans=-2147483647;
int num[16010],dp[16010];
void dfs(int,int);
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",num+i);
for(int i=1;i<=n-1;i++)
{
int a,b;
scanf("%d%d",&a,&b);
tree[a].push_back(b);
tree[b].push_back(a);
}
dfs(1,0);
printf("%d",ans);
return 0;
}
void dfs(int root,int hisfather)
{
dp[root]=num[root];
for(int i=0;i<tree[root].size();i++)
{
int to=tree[root][i];
if(to==hisfather)continue;
dfs(to,root);
dp[root]+=max(0,dp[to]);
}
ans=dp[root]>ans?dp[root]:ans;
}
我本人见过的树形DP也就上面几种,以后遇到别的还会补充.