树形Dp计算树上的dp,就是在树的结构上运用dp思想。
而树形dp大多用的是递归,因为对于树的节点,深度复杂,不太能用for循环去枚举状态。
https://www.luogu.com.cn/problem/P1352
一道树型dp模板题,简单了解树型dp。
题目就是上司和员工这样一层一层的树型结构。
我们可以选择上司,放弃员工,也可以选择员工,放弃上司。
这样我们就可以用dp[i][0/1]表示到第i个为止 选/不选 第i个所能获得的最大效益。
这样我们就枚举每个节点的子节点(每个上司的员工),转移方程就是:
f[i][0]+=max(f[j][1],f[j][0]); 不选第i个点
f[i][1]+=f[j][0]; 选第i个点
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <bits/stdc++.h>
#define ll long long
#define MAX 6010
using namespace std;
vector<int> child[MAX];
int visit[MAX]; 用来记录子节点,用于后面寻找根节点
int a[MAX],f[MAX][2];
void dfs(int i){
f[i][0]=0;
f[i][1]=a[i-1];
//枚举子节点
for(int k=0;k<child[i].size();k++){
int y=child[i][k];
dfs(y);
f[i][0]+=max(f[y][1],f[y][0]) ;
f[i][1]+=f[y][0];
}
}
int main()
{
std::ios::sync_with_stdio(false);
int n,x,y;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
for(int i=0;i<n-1;i++){
scanf("%d%d",&x,&y);
child[y].push_back(x);
visit[x]=1;
}
int root;
for(int i=1;i<=n;i++)
if(!visit[i]){
root=i;
break;
}
dfs(root);
printf("%d\n",max(f[root][0],f[root][1]));
}
https://www.luogu.com.cn/problem/P2015
题目要求 q个树枝下能最多有多少个苹果。二叉苹果树,很明显的树型。就是要选取一些树枝求最多苹果。当然树枝是要连在一起的,中途不能断开。这样我们就能转换为求左子树最多和右子树最多。
这样我们要维护的值就是 当前的节点 i 和 j 个树枝
dp[i][j]表示根节点为i且树枝为j的最多苹果。
转移方程就为:
dp[ i ][ j ]=max(dp[ i ][ j ],dp[ i ][j-k-1]+dp[ y ][ k ] +val[ i ][ y ] )
就是求i为根节点 转为求 y为根节点 有k个树枝+i为根节点,有j-k-1个树枝(这里减一是因为y和i中间还连了一根)+ i 和 y这条边的苹果数量
题目中没有给出节点的父子关系,所以我们用双向数组存。
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,q;
int val[110][110],visit[110],f[110][110];
vector<int> child[110];
void dfs(int x){
visit[x]=1;
for(int i=0;i<child[x].size();i++){
int y=child[x][i];
//用双向数组存的,所以里面可能有父节点
if(visit[y]==1) continue;
visit[y]=1;
dfs(y);
//枚举树枝的个数
for(int m=q;m>0;m--)
//枚举字节点的树枝的个数
for(int k=m-1;k>=0;k--)
f[x][m]=max(f[x][m],f[y][k]+f[x][m-k-1]+val[x][y]);
}
}
int main()
{
std::ios::sync_with_stdio(false);
scanf("%d%d",&n,&q);
int x,y,z;
for(int i=0;i<n-1;i++) {
scanf("%d%d%d",&x,&y,&z);
val[x][y]=z;
val[y][x]=z;
child[x].push_back(y);
child[y].push_back(x);
}
dfs(1);
printf("%d\n",f[1][q]);
}
https://www.luogu.com.cn/problem/P3177
dp[ i ][ j ]表示以i为根节点,j个黑点个数对结果的贡献
最主要的是两两之间的距离怎么求。
我们将其装换为两两的路径和,然后我们考虑每一条边的贡献。
如果两个黑点在边的同侧,那么对结果无贡献,如果在两侧,则这条边必经过两次。
如这条边左边有两个黑点,右边两个黑点,则算这四个黑点两两的距离则
算这条边对其贡献度为 2*2 *v=4 * v
那么一条边的贡献度为这条边的权值 * (这条边两侧的黑点乘积+白点乘积)
邻接表存储树https://blog.youkuaiyun.com/xiang_yu_pai/article/details/104642609
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int size=2020;
ll dp[size][size];
int head[size],dig[size]; //dig[i]表示以 i 为根点的子树的节点数量
int cnt,N,K;
struct node{
ll y,nex,v;
}e[size<<1+1];
void add(int a,int b,int w){ //邻接表存储树
e[++cnt].y=b;
e[cnt].v=w;
e[cnt].nex=head[a];
head[a]=cnt;
}
void dfs(int x,int father){
dig[x]=1;
dp[x][0]=0,dp[x][1]=0; //不管黑点为0或1,都是合法的情况
for(int i=head[x];i!=0;i=e[i].nex){
int y=e[i].y;
if(y==father) continue; //双向存储,有包含父节点的情况,过掉
dfs(y,x);
dig[x]+=dig[y];
for(int j=min(K,dig[x]);j>=0;j--){ //x的子树最多有dig[x]个节点
for(int k=0;k<=min(j,dig[y]);k++){ //y的子树最多有dig[y]个节点
if(dp[x][j-k]==-1) continue; //不合法的情况
ll t=e[i].v*( (K-k)*k + (dig[y]-k)*(N-dig[y]+k-K));
dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]+t);
}
}
}
}
int main()
{
int a,b,c;
scanf("%d%d",&N,&K);
memset(dp,-1,sizeof(dp));
for(int i=1;i<N;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); //不知道节点的父子关系,所以用双向存储
add(b,a,c);
}
dfs(1,0);
printf("%lld\n",dp[1][K]);
}