树形背包
树形背包的一般形式
给定一棵有\(n\)个节点的点权树,要求从中选出\(m\)个节点,使得这些选出的节点的点权和最大,一个节点的父节点被选之后才可以选择。题目一般是一个物品只能依赖一个物品,但一个物品可以被多个物品依赖。
板子题:选课
- 题面:给定\(N\)门课程,每门课有一个学分,每门课有一门或没有先修课,一个学生从中选\(M\)门课程学习,问能获得的最大学分。
- 数据范围:\(1 \le N \le 300,1 \le M \le 300\)
因为直接给出了爸爸和儿子,所以存单向边就好了。又因为\(S_i\)为节点的权值所以开个val直接记录就好了。
令dp[x][j]表示在x的子树上选j个节点的最大值
\(dp\)的式子很好想:\(dp[fa][j]=max(dp[x][j-1])\)
const int N=305;
int n,m,val[N],dp[N][N];
int cnt,head[N],nxt[N],to[N];
inline void add(int x,int y){
nxt[++cnt]=head[x];
to[cnt]=y;
head[x]=cnt;
}
void dfs(int x,int tot){
if(tot<=0)return ;
for(int i=head[x],y;i;i=nxt[i]){
y=to[i];
for(int j=0;j<tot;j++){//注意这里$j \not= tot$,如果j=tot那x都取不了
dp[y][j]=dp[x][j]+val[y];
}
dfs(y,tot-1);//如果选择y
for(int j=1;j<=tot;j++){//j从1开始,y不能选-1个节点
dp[x][j]=max(dp[x][j],dp[y][j-1]);
}
}
}
int main(){
n=read();m=read();
for(int i=1,k,s;i<=n;i++){
k=read();s=read();
add(k,i);
val[i]=s;
}
dfs(0,m);
printf("%d",dp[0][m]);
return 0;
}
复杂度为\(O(n^3)\)
因为遍历整棵树是\(O(n)\)的,而选取\(i\)和\(j\)是\(O(m^2)\)的所以整个程序的复杂度为\(O(nm^2)\)
例题:送给好友的礼物
- 题面:给定一棵包含\(n\)个节点的树\(T\),节点从\(1 - n\)顺序编号(1号节点一定是根节点),时刻0在1号节点的两个人想要经过\(k\)个节点,并回到节点1,求最少用时。
- 数据范围:\(1 \le k \le n \le 415\)
很显然是个\(dp\),但并不影响我不会。
状态设计很有意思:设\(dp[i][j]\)表示遍历完以\(i\)为根的子树,其中第一个人走了\(j\)步,第二个人最少走多少步。则答案为\(max(dp[i][j],j)\)
又因为没有目标点的子树用不到,所以在一开始DFS的时候删掉就行
bool dfs(int x){
bool flag=0;//表示当前子树有无草莓
for(int i=head[x],y;i;i=nxt[i]){
y=to[i];
if(!y||y==fa[x])continue;
fa[y]=x;
bool now=dfs(y);
if(!y)to[i]=0;//没有草莓就把y子树删掉
flag|=now;
}
return flag|book[x];
}
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=425;
int n,k;
int cnt,head[N],nxt[N<<1],to[N<<1];
inline void add(int x,int y){
to[++cnt]=y;
nxt[cnt]=head[x];
head[x]=cnt;
}
bool book[N];
int fa[N],siz[N],dp[N][N<<1];
bool dfs(int x){
bool flag=0;
for(int i=head[x],y;i;i=nxt[i]){
y=to[i];
if(!y||y==fa[x])continue;
fa[y]=x;
bool now=dfs(y);
if(!now)to[i]=0;
flag|=now;
}
return flag|book[x];
}
void sol(int x){
siz[x]=1;
dp[x][0]=0;
for(int i=head[x],y;i;i=nxt[i]){
y=to[i];
if(!y||fa[x]==y)continue;
sol(y);
siz[x]+=siz[y];
for(int j=(siz[x]-1)/*边数*/*2;j>=0;j--){//枚举第一个人走了i步,上界为整棵树的边数*2(往返),注意从大到小枚举
dp[x][j]=dp[x][j]+dp[y][0]+2/*往返一趟y节点*/;//若第一个人不进入y子树,第一个人不会走边(u,v),利用上一次的dp值进行更新
if(j>=siz[y]*2)dp[x][j]=min(dp[x][j],dp[x][j-siz[y]*2]+dp[y][(siz[y]-1)*2]);
for(int k=min((siz[y]-1)*2,j-2);k>=0;k--){
dp[x][j]=min(dp[x][j],dp[x][j-k-2]+dp[y][k]+2);
}
}
}
}
int main(){
memset(dp,0x3f3f3f3f,sizeof dp);
n=read();k=read();
for(int i=1,u,v;i<n;i++){
u=read();v=read();
add(u,v);add(v,u);
}
for(int i=1;i<=k;i++){
book[read()]=1;
}
dfs(1);
sol(1);
int ans=0x3f3f3f3f;
for(int i=0;i<=(siz[1]-1)*2;i++){
ans=min(ans,max(i,dp[1][i]));
}
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号