多叉树的树形背包常见建模方法
来源:互联网 发布:电脑像素绘画软件 编辑:程序博客网 时间:2024/05/20 13:16
一.多叉树变二叉树。
这个技巧其实也有两种具体的方法:树的孩子兄弟表示法与dfs序法。
1.树的孩子兄弟表示法。
大家在学习树形结构时一定接触了一个多叉树变二叉树的方法,就是把每个点与它的第一个儿子连边,然后将它的儿子依次连接起来。可以结合下面的图片理解这句话。
总结成口诀就是:第一个儿子是左子树,其他儿子是左子树的右子树(似乎并不押韵,手动滑稽)
2.dfs序法
dfs序就是对一个树进行一个dfs,然后对于每个点记录一个时间戳dfn数组,这个时间戳就是这个节点的dfs序,然后我们再记录一个size数组,表示以每个节点为根的子树中节点的数量。
假设根节点是u,那么可以容易的推出
第一个儿子的dfs序dfn[first_son]就是dfn[u]+1
第二个儿子的dfs序dfn[second_son]就是dfn[u]+size[first_son]+1
其余同理。
那么u的下一个兄弟的dfs序dfn[next_brother]就是dfn[u]+size[u]+1
这两个方法大多用于树形依赖形背包(即使用一个节点必须要使用它所有的祖先),
主要解决点权问题。
主要作用就是可以使决策转移的过程变成O(1)的了。
最常见的模型就是:有n个物品,有一个m大小的包,每个物品有wi物重与vi物品价值,物品之间存在只有装了物品a,才能装物品b的n-1条关系(就是一个树)。问能取得的最大价值。
简要分析:显然是一个多叉树,考虑转换。
1.孩子兄弟表示法:对于一个节点i,设dp[i][j]表示在以i为根的子树中,用大小为j的包能取得的最大价值,那么dp[i][j]=max(dp[left[i]][j-w[i]]+v[i],dp[right[i]][j])
注意,这里的left[i]是i在原树中的第一个儿子,right[i]是i在原树中的下一个兄弟。
这个方程是非常好理解的。效率就是O(nm)的。
2.dfs序法:对于一个dfs序为i的节点u,同样设dp[i][j]表示在以u为根的子树中,用大小为j的包能取得的最大价值,那么dp[i][j]+v[i]->dp[i+1][j-w[i]]
dp[i][j]->dp[i+size[i]+1][j]
注意,这里的转移并不是常见的dp[i][j]=max()....(用dp[i][j]的前驱状态去计算dp[i][j]),而是用dp[i][j]去更新它的后继状态。这种方法被称为刷表法。
两种方法都是非常巧妙的。但作用也是有限的,只能解决依赖性背包中的点权问题。
二.分组的树形背包。
这类问题也是有一个常见模型的,具体可参考洛谷P1272重建道路。
下面针对这道题来分析,能够解决多叉树的,分组的树形背包。
此时,我们的儿子与父亲之间并不存在依赖型关系,那么我们设dp[k][i][j]表示以i为根的子树,在前k个儿子中,分离出一个大小为j的子树(必须包含i),所需要最少的操作次数。
那么我们每计算到第k+1个新的儿子v时(full_son[v]表示v的儿子个数),
dp[k+1][i][j]=min(dp[k][i][j-t]+dp[full_son[v]][v][t]);
由于一个树形关系,我们需要在一个dfs上进行dp,即先dfs(v),然后更新dp[k+1][i][j]。
这个k的一维显然可以用滚动数组优化掉。
那么就是
j=m->1
t=1->j
dp[i][j]=min(dp[i][j-t]+dp[v][t]);
同时,dp一律要注意初始化,即刚开始时所有的dp[i][1]=du[i](du[i]表示与i连边的节点数,又称i的入度(树是无向边哟!))
给出参考代码,方便理解:
#include<cstdio>#include<algorithm>#include<cstring>using namespace std;const int INF=0x3f3f3f3f;const int N=201;struct Edge{int to,next;}e[N*2];int du[N],a[N],dp[N][N];int n,k,res=INF,EdgeCnt=0;void addedge(int u,int v){int p=++EdgeCnt;e[p].to=v;e[p].next=a[u];a[u]=p;}void dfs(int u,int fa){dp[u][1]=du[u];for (int p=a[u];p;p=e[p].next){int v=e[p].to;if (v!=fa){dfs(v,u);for (int j=k;j>=1;j--)for (int k=1;k<=j;k++)dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);}}res=min(res,dp[u][k]);}int main(){scanf("%d%d",&n,&k);memset(dp,0x3f,sizeof(dp));for (int i=1;i<n;i++){int u,v;scanf("%d%d",&u,&v);addedge(u,v);addedge(v,u);du[u]++;du[v]++;}dfs(1,0);printf("%d",res);return 0;}
同样,这个方法也是有缺陷的,就是无法解决点权问题。大多数运用于边权问题。点权当然也可以,但是效率较低。
最后总结一句:树形背包都与dfs离不开关系,所以我们可以在dfs上dp可以写的更简单,也可以在dfs预处理后再总刷表法dp。
这三种方法都各有长处,各有短处,实际运用时还是要注意题目本身的。
- 多叉树的树形背包常见建模方法
- 数学建模常见的综合评价方法及预测方法
- 常见的目标检测中的背景建模方法漫谈
- 常见的目标检测中的背景建模方法漫谈
- 常见的目标检测中的背景建模方法漫谈
- 常见的目标检测中的背景建模方法漫谈
- 常见的目标检测中的背景建模方法漫谈
- 常见的目标检测中的背景建模方法
- 常见的目标检测中的背景建模方法漫谈
- hdu 3593 树形依赖背包的优化
- hdu1561有依赖的背包-树形dp
- 有树形依赖的背包问题
- Background Subtraction and Modeling 常见的目标检测中的背景建模方法漫谈
- Background Subtraction and Modeling 常见的目标检测中的背景建模方法漫谈
- Background Subtraction and Modeling 常见的目标检测中的背景建模方法漫谈
- Background Subtraction and Modeling 常见的目标检测中的背景建模方法漫谈
- Background Subtraction and Modeling 常见的目标检测中的背景建模方法漫谈
- UML建模中常见的关系
- matlab2c使用c++实现matlab函数系列教程- polyval函数
- 第1课:软件工程师该怎么了解 FPGA 架构
- PAT乙级1057. 数零壹(20)
- HDU 1257 最少拦截系统
- 基于Nginx的软件负载均衡实现解读
- 多叉树的树形背包常见建模方法
- LightOJ
- 物联网智能硬件设备常见攻击方法
- 侧拉和横滑菜单 请求数据以及多条目
- Mybatis缓存
- aix常用命令
- 存储过程
- hp-nuix常用命令
- 常见流程语句