背景知识:链式前向星,DFS,线性DP,背包,树形结构
先刷几天题,我看网上根本就没个入门的树形DP文章,等刷完题看看能不能写个好点的文章
上来就是俩绿题,毕竟树形DP没简单题
没有上司的舞会,树上DP入门
分析:给定树上每个点的权值,并且以u为根节点时,如果选择了u的权值那么u的儿子的权值就不能选取,如果选取了u的儿子节点的权值,那么u的权值就不能选取。
设计dp[i][j]为节点i分别在选与不选情况下的最大值,dp[i][0]代表不选,dp[i][1]代表选
我们很容易的能写出方程
dp[i][0]=max(max(dp[i的儿子][1]+dp[i][0],dp[i][0]),max(dp[i的儿子][1],dp[i的儿子][0]));
dp[i][1]=max(max(dp[i][1],dp[i][1]+dp[i的儿子][0]),dp[i的儿子][0]);
也能看出这里我们需要循环遍历i的所有儿子。
但是我们是要从上往下还是从下往上去实现dp过程呢?
显然,如果从上往下,我们无法处理子节点传给父节点的后效性。
#include <iostream>
using namespace std;
#define N 6000+100
struct node
{
int to,nex;
};
node edge[N<<1];
int head[N],tot,rd[N];
int dp[N][2];
void add(int from,int to)
{
edge[++tot].to=to;
edge[tot].nex=head[from];
head[from]=tot;
}
void DFS(int x)//重点
{
for(int i=head[x];i;i=edge[i].nex)
{
DFS(edge[i].to);
dp[x][1]=max(max(dp[x][1],dp[x][1]+dp[edge[i].to][0]),dp[edge[i].to][0]);
dp[x][0]=max(max(dp[x][0],dp[edge[i].to][1]+dp[x][0]),max(dp[edge[i].to][1],dp[edge[i].to][0]));
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>dp[i][1];
}
for(int i=1,from,to;i<=n-1;i++)
{
cin>>from>>to;
rd[from]++;
add(to,from);
}
int root=0;
for(int i=1;i<=n;i++)
{
if(rd[i]==0)
{
root=i;
break;
}
}
DFS(root);
cout<<max(dp[root][0],dp[root][1])<<endl;
return 0;
}
二叉苹果树,书上背包模板
莫说二叉,二十叉也一样
分析:给定了每条边的权值,我们需要保留m条边,使得我们保留的边的总权值最大,很明显,我们有一个容量为m的背包,在树这个货物之间会互相影响的货堆里要求把背包整的权值最大。
那么我们设计dp状态为
dp[i][j]:以i为根节点的子树,保留m条边获得的最大权值。
状态方程有
dp[now][j]=max(dp[now][j],dp[now][j-k-1]+dp[e[i].to][k]+e[i].dis);
其中e[i].to是目前now的儿子节点
e[i].dis是链接now和now的儿子节点的边
翻译过来就是
now节点为根的子树,保留j条边的最大值,就是
1.now节点保留j-k-1条边加上儿子节点保留k条边加上链接now和now的儿子节点的边的权值和
2.now节点保留j条边
1 2取最大值
#include <bits/stdc++.h>
#define maxn 105
using namespace std;
int n,m,dp[maxn][maxn];
struct node
{
int dis,to,next;
};
node e[maxn<<1];
int tot,head[maxn];
void add(int from,int to,int dis)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
e[tot].dis=dis;
}
int sz[maxn];
void dfs(int now,int fa)
{
for(int i=head[now];i;i=e[i].next)
{
if(e[i].to==fa)
continue;
dfs(e[i].to,now);
sz[now]+=sz[e[i].to]+1;
for(int j=min(sz[now],m);j;--j)
{
for(int k=min(sz[e[i].to],j-1);k>=0;--k)
{
dp[now][j]=max(dp[now][j],dp[now][j-k-1]+dp[e[i].to][k]+e[i].dis);
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1,u,v,w;i<n;++i)
{
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(1,0);
cout<<dp[1][m]<<endl;
return 0;
}
通过两题总结一下就是
dp状态一般是以u为根节点的子树怎样怎样,或者是u节点怎样怎样
dp过程一般是从下往上
dp每个节点通常和此节点的所有儿子节点有关