https://www.luogu.org/problemnew/show/P2014


树形背包的裸题。。当版子好了。

$f[i][j][k]$表示子树$i$选前$j$个孩子,共$k$个后代节点时的最大价值。然后$j$那一维是可以滚动的(但同时也要注意枚举变成了倒序),所以可以去掉。

$f[i][j]$表示子树$i$共选$k$个后代节点时的最大价值。

然后每个点可以抽象为一个背包,他的每个孩子包含一物品,一组物品中包括以孩子为子树,选v个其后代节点形成的最大价的共v+1个物品(1指的是只有孩子自己)。对于每个孩子,只能选他的一种状态情形,或者不选。所以就是一个分组背包啦。

但是注意,子树的根必须强制选上。所以可以以他为初态,也就是后代节点=0的状态。写一下伪代码。

$dp$  $i$

$f_{i,0}=w_i$初态

$for$  $j=1$ $\sim$ $son_i$

  $for$  $k=$(倒序)$sum_i -1$ $\sim$ $1$

    $for$  $v=0$ $\sim$ $sum_j -1$

      $if$  $v+1\leqslant k$

        $f_{i,k}=max\{f_{i,k-v-1}+f_{j,v}\}$

然后复杂度由于是每个点都被考虑一次的,最坏是$O(N^3)$。(看做$N,M$同阶)

  #include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define dbg(x) cerr<<#x<<" = "<<x<<endl
#define ddbg(x,y) cerr<<#x<<" = "<<x<<" "<<#y<<" = "<<y<<endl
using namespace std;
typedef long long ll;
template<typename T>inline char MIN(T&A,T B){return A>B?A=B,:;}
template<typename T>inline char MAX(T&A,T B){return A<B?A=B,:;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=+;
int f[N][N],Head[N],Next[N<<],to[N<<],w[N],sum[N],tot;
int n,m;
inline void Addedge(int x,int y){
to[++tot]=y,Next[tot]=Head[x],Head[x]=tot;
to[++tot]=x,Next[tot]=Head[y],Head[y]=tot;
}
#define j to[tmp]
void dp(int i,int fa){
sum[i]=;
for(register int tmp=Head[i];tmp;tmp=Next[tmp])if(j!=fa)dp(j,i),sum[i]+=sum[j];
f[i][]=w[i];
for(register int tmp=Head[i];tmp;tmp=Next[tmp])if(j!=fa){
for(register int k=sum[i]-;k;--k){
for(register int v=;v<=sum[j]-;++v)
if(v+<=k)MAX(f[i][k],f[i][k-v-]+f[j][v]);
}
}
}
#undef j
int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);
read(n),read(m);int x;
for(register int i=;i<=n;++i)read(x),read(w[i]),Addedge(x,i);
dp(,);printf("%d\n",f[][m]);
return ;
}

然而这题有更优秀的(优化)做法。复杂度(看做$N,M$同阶)都是$O(N^2)$.

1.根据上面的一种针对性优化。

由于上面每次合并$i$的答案可以视作是子树$j$和$i$的已合并部分做一个$f[i,k+v]<--f[i,k]+f[j,v]$。同时又有“每个点费用都是$1$”这样一个隐含的特殊条件,

所以如果每次合并都枚举整棵树大小的费用未免浪费。把枚举改成:枚举$i$已合并部分点的个数$k$和价值$f[i,k]$,再枚举子树$j$的点个数$v$和价值$f[j,v]$,更新之。

就是这样。比较一下和上面的这个的区别。

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define dbg(x) cerr<<#x<<" = "<<x<<endl
#define ddbg(x,y) cerr<<#x<<" = "<<x<<" "<<#y<<" = "<<y<<endl
using namespace std;
typedef long long ll;
template<typename T>inline char MIN(T&A,T B){return A>B?A=B,:;}
template<typename T>inline char MAX(T&A,T B){return A<B?A=B,:;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=+;
int f[N][N],Head[N],Next[N<<],to[N<<],w[N],sum[N],tot;
int n,m;
inline void Addedge(int x,int y){
to[++tot]=y,Next[tot]=Head[x],Head[x]=tot;
to[++tot]=x,Next[tot]=Head[y],Head[y]=tot;
}
#define j to[tmp]
void dp(int i,int fa){
sum[i]=;f[i][]=w[i];
for(register int tmp=Head[i];tmp;tmp=Next[tmp])if(j!=fa){
dp(j,i);
for(register int k=sum[i]-;~k;--k)
for(register int v=;v<=sum[j]-;++v)
MAX(f[i][k+v+],f[i][k]+f[j][v]);
sum[i]+=sum[j];
}
}
#undef j
int main(){//freopen("test.in","r",stdin);freopen("test.out","w",stdout);
read(n),read(m);int x;
for(register int i=;i<=n;++i)read(x),read(w[i]),Addedge(x,i);
dp(,);printf("%d\n",f[][m]);
return ;
}

树中每个点对相当于只会被在LCA处合并$f[lca,k+v]$枚举一次(可以把枚举的个数$k,v$看做子树中的编号)。于是枚举了$O(n^2)$个点对。所以是平方复杂度。

但是,这只是针对性的优化,也就是说,如果改成每个点都有一个费用且不一定为1,这样每个点做一次分组背包时就要完全枚举费用这一维了,没有办法用点对优化。

2.更高效的dfs序优化。

给定一棵 $n $个节点的树,$1$ 号节点是根节点。每个点有一个物品,价格为 $c_i$ ,价值为 $v_i$ 。
要买一个点上的物品,必须先把它父节点的物品给买了。求 $m$ 元钱能买到的最大价值。$n,m ≤ 2000$。

这时无法用点对优化。因为树上dp是按照一定顺序(dfs序)进行的,所以考虑转化到dfs序列上处理。设得到的dfs序中,$i$对应原序列点编号$p_i$,这个$p_i$子树dfs序上右端点设为$r_i$。

设$f_{i,j}$为dfs序上选择$i\sim n$中的点且满足树形要求的、费用为$j$的最大价值。

则$f_{i,j}=max(f_{i+1,j-cost_{p_i}}+value_{p_i},f_{r_i+1,j})$。

注意是倒序以使得先处理所有子代再处理子树根的。相当于决定当前子树的根如果选,那么他的子树内部和后面的dfs序都可以随便选。如果不选,那子树这一段的dfs序都不可选,直接从另一颗子树中继承过来。

可知若后面的点$i+1$若满足树形依赖的要求,则dp了$i$之后$i$只可能包含这个子树没选和$i$和$i+1$都选了($i+1 \sim r_i$满足树形依赖,则选$i$后也应当满足依赖关系)的情况。

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define dbg(x) cerr << #x << " = " << x <<endl
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,):;}
template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,):;}
template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=+;
int f[N][N],c[N],val[N];
int n,m,cnt;
struct thxorz{int to,nxt;}G[N<<];
int Head[N],id[N],ed[N],tot;
inline void Addedge(int x,int y){
G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot;
G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot;
}
#define y G[j].to
inline void dfs(int x,int fa){
id[++cnt]=x;
for(register int j=Head[x];j;j=G[j].nxt)if(y^fa)dfs(y,x);
ed[x]=cnt;
}
#undef y
int main(){//freopen("test.in","r",stdin);freopen("test.ans","w",stdout);
read(n);read(m);
for(register int i=,x;i<=n;++i)read(x),read(val[i]),Addedge(x,i),c[i]=;//read(c[i]);
dfs(,);
for(register int i=cnt;i;--i)
for(register int j=c[id[i]];j<=m;++j)
f[i][j]=_max(f[i+][j-c[id[i]]]+val[id[i]],f[ed[id[i]]+][j]);//dbg(i),dbg(j),dbg(f[i][j]);
printf("%d\n",f[][m]);
return ;
}

还有一种转二叉树的做法,不想了解。

这篇题解赶完了。

luogu2014 选课[树形背包][优化成$O(n^2)$的方法]的更多相关文章

  1. 选课 树形背包dp

    题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分,每门课有一 ...

  2. 洛谷 P2014 选课(树形背包)

    洛谷 P2014 选课(树形背包) 思路 题面:洛谷 P2014 如题这种有依赖性的任务可以用一棵树表示,因为一个儿子要访问到就必须先访问到父亲.然后,本来本题所有树是森林(没有共同祖先),但是题中的 ...

  3. [Luogu2014]选课(树形dp)

    [Luogu2014]选课 题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课 ...

  4. Codevs1378选课[树形DP|两种做法(多叉转二叉|树形DP+分组背包)---(▼皿▼#)----^___^]

    题目描述 Description 学校实行学分制.每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分.学校开设了N(N<300)门的选修课程,每个学生可选课程的数量M是给定的.学生选修 ...

  5. POJ 1155 (树形DP+背包+优化)

    题目链接: http://poj.org/problem?id=1155 题目大意:电视台转播节目.对于每个根,其子结点可能是用户,也可能是中转站.但是用户肯定是叶子结点.传到中转站或是用户都要花钱, ...

  6. 洛谷 P1273 有线电视网(树形背包)

    洛谷 P1273 有线电视网(树形背包) 干透一道题 题面:洛谷 P1273 本质就是个背包.这道题dp有点奇怪,最终答案并不是dp值,而是最后遍历寻找那个合法且最优的\(i\)作为答案.dp值存的是 ...

  7. 【洛谷 P2515】 [HAOI2010]软件安装 (缩点+树形背包)

    题目链接 看到代价和价值这两个关键词,肯定是首先要想到背包的. 但是图中并没有说这是棵树,所以先要\(Tarjan\)缩点,然后就是选课了,跑一遍树形背包就好了. 注意:缩点后应该是一个森林,应该用一 ...

  8. Kattis - redblacktree Red Black Tree (树形背包)

    问题:有一课含有n(n<=2e5)个结点的数,有m(m<=1000)个结点是红色的,其余的结点是黑色的.现从树中选若干数量的结点,其中红色的恰有k个,并且每个结点都不是其他任何另一个结点的 ...

  9. [POJ1155]TELE(树形背包dp)

    看到这道题的第一眼我把题目看成了TLE 哦那不是重点 这道题是树形背包dp的经典例题 题目描述(大概的): 给你一棵树,每条边有一个cost,每个叶节点有一个earn 要求在earn的和大于等于cos ...

随机推荐

  1. 增加游戏受众:在英特尔核显上运行MR游戏

    为了获得良好的用户体验,通常VR游戏需要稳定在每秒90帧,这意味着整个场景只有11.1毫秒的时间来进行左右眼渲染,这就是为何VR游戏需要配备高端的独立显卡.现在来看看我们如何使用最新版英特尔图形性能分 ...

  2. spring @valid 注解

    用于验证注解是否符合要求,直接加在变量之前,在变量中添加验证信息的要求,当不符合要求时就会在方法中返回message 的错误提示信息. @PostMapping public User create ...

  3. 汉诺塔问题的C++实现

    有三根杆子A,B,C.A杆上有N个(N>1)穿孔圆环,盘的尺寸由下到上依次变小.要求按下列规则将所有圆盘移至C杆:每次只能移动一个圆盘:大盘不能叠在小盘上面.如何移?最少要移动多少次? 原理可参 ...

  4. 解决Iframe跨域高度自适应,利用window.postMessage()实现跨域消息传递页面高度(JavaScript)

    在iframe跨域引用高度自适应这块写的js方式都试了不管用,最终使用的是window.postMessage() 跨域获取高度 传递信息 1.首先,在主页面上使用iframe引入子页面:也就是A.h ...

  5. Oracle-DDL 3- 触发器

    DDL-数据定义语句: 四.触发器 -- 触发器(trigger),与某些特定的操作绑定,当达到触发条件后会自动触发--主要用来将某些操作的过程记录下来,或者阻止某些非法的操作,保护数据 /*触发条件 ...

  6. C++练习 | 铁轨问题

    #include <iostream> #include <cmath> #include <cstring> #include <string> #i ...

  7. MySQL的事务和锁

    MySQL的事务和锁   阅读目录 什么是事务 事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再 ...

  8. python之成像库pillow

    目录 python之成像库pillow 官方文档 图像模块(Image.Image) Image模块的功能 Image.new(mode,size,color): Image.open(file,mo ...

  9. Ruby学习中(哈希变量/python的字典, 简单的类型转换)

    一. 哈希变量(相当于Python中的字典) 详情参看:https://www.runoob.com/ruby/ruby-hash.html 1.值得注意的 (1). 创建Hash时需注意 # 创建一 ...

  10. MongoDB v4.0 命令

    MongoDB v4.0 命令 官方文档 > 点这里 < 操作系统库 #操作管理员库 use admin #鉴权 db.auth("root","admin& ...