总目录
在线测评地址(ybt)
在线测评地址(LOJ)
在线测评地址(LUOGU)
样例作图如下:
发现是两棵树。
每棵树都可以采用01背包处理,那树与树之间该如何拼接呢,继续01背包吗?
突然发现,样例数据可以进一步如下图处理:
树根的序号定为0,多树就合并成了一棵树,将学分固定在树枝上。01背包就可以畅快的使用了。
具体算法如下:
sz[u]表示u的子树上的边数,可以通过计算u的子节点数量得到,请看样例中1的子节点数量是4,子树上的边数也是4。
设f[u][j]表示u的子树上保留j条边,至多保留的课程数目
那么状态转移方程也就显而易见了
f[u][i]=max(f[u][i],f[u][i-j-1]+f[v][j]+e[i].w)
f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+e[i].w) ( 1≤j≤min(M,sz[u]),0≤k≤min(sz[v],j−1) )
u表示当前节点,v是u的一个子节点,sz[u]表示u的子树上的边数,M就是题目中要求的最多保留边数
那么为什么是这个方程呢?
首先,为什么是f[u][j-k-1]而不是f[u][j-k]?
为前文提到了,保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从u的子节点v的子树上留边的话,也要留下u,v之间的连边
那么取值范围k为什么要小于等于j−1而不是j呢?
同上,因为要保留u,v连边
对了,别忘了i,j要倒序枚举(数组少开了一维)因为这是01背包
更详细的01背包深入说明:点击这里
ybt
通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 640KB | 2MS |
测试点2 | 答案正确 | 636KB | 2MS |
测试点3 | 答案正确 | 632KB | 2MS |
测试点4 | 答案正确 | 632KB | 1MS |
测试点5 | 答案正确 | 632KB | 1MS |
测试点6 | 答案正确 | 868KB | 2MS |
LOJ
LUOGU
AC代码如下:
#include <bits/stdc++.h>
#define maxn 310
using namespace std;
int N,M;
struct node{
int to,w,next;
}e[maxn<<1];
int head[maxn],tot,sz[maxn],f[maxn][maxn];//sz[]记录节点对应的子树节点数量
void add(int u,int v,int w){//邻接表形式加入边
tot++,e[tot].to=v,e[tot].w=w,e[tot].next=head[u],head[u]=tot;
}
void init(){
int u,v,w;
scanf("%d%d",&N,&M);
for(v=1;v<=N;v++){
scanf("%d%d",&u,&w);
add(u,v,w),add(v,u,w);
}
}
void dfs(int u,int fa){
int i,j,k,v;
for(i=head[u];i;i=e[i].next){
v=e[i].to;
if(v==fa)continue;
dfs(v,u);
sz[u]+=sz[v]+1;
for(j=min(sz[u],M);j;j--)
for(k=min(sz[v],j-1);k>=0;k--){
f[u][j]=max(f[u][j],f[u][j-1-k]+f[v][k]+e[i].w);
}
}
}
int main(){
init();
dfs(0,-1);
printf("%d\n",f[0][M]);
return 0;
}
该题收获:
自根向树叶节点遍历,自树叶节点向根节点开始做事。
树的子节点数量求法。
树上使用01背包。
自创数据对01背包进行模拟,更深入的了解了01背包空间上降维的原因。