动态规划

4.1 背包问题

01背包

题目:给n个物品,每个物品有一个价值w[i],都需要占用一定的空间v[i],在总空间一定的情况下V,求最大的价值,物品不可拆分。

分析:状态用二维数组dp[i,j]表示,第一维表示到了第i个物品,第二维表示当前占了j空间的总价值。循环每个物品,然后遍历每个空间值,从dp[i,j]的转移就是: dp[i+1,j+v[i+1]]=max(dp[i+1,j+v[i+1]],dp[i,j]+w[i+1])。

二维数组表示状态理解了,然后理解为什么可以用一维数组表示状态。在二维的转移的过程中,我们会发现,后更新的空间值一定大于前更新的,这样的话,如果从后往前更新也是可以的,而且不会影响到前面,那么更新的状态和未更新的状态就可以合并到一个维度上。对于一维状态,从后往前转移,这样保证后面的转移不会影响到前面的结果,从而节省了一维的空间,时间复杂度不变。

核心代码:  for (i=1; i<=N; i++){   for (v = V; v>=v[i]; v--){    dp[v] = max ( dp[v], dp[v-v[i]] + w[i] );         }  }  

例题:http://poj.org/problem?id=3628


[编辑] 完全背包问题

题目:条件与01背包基本相同,不同的是,每个物品的个数是没有限制的。

分析:虽然个数没有限制,但是背包的容量是有限制的,所以个数还是有限制的。这样的话,就可以化为01背包,就是否取1个,是否取2个,这样做的话,时间复杂度会很高。按照01背包更新,我们会发现i个物品的状态都可以从i-1个物品更新出来,就是说我们会重复更新很多次。

由于个数没有限制,如果我们从空间从小到大循环,即本次循环更新的状态,到后面的更新仍然可以用,一个物品就可以被更新多次,这样就可以保证个数的不受限制。理解与01背包的不同,为什么两者循环的顺序不同。

核心代码:  for ( i=1; i<=N; i++){   for (v=0; v<=V-cost[i]; v++){   dp[v+v[i]] = max( dp[v+v[i]], dp[v]+w[i] );         }  }  

例题:http://poj.org/problem?id=2063


[编辑] 多重背包问题

题目:其他与上题相同,每个物品的数量固定num[i]。

分析:这样的话,我们最简单的方法就是把多个物品拆成01背包去做,但是时间会很多。每个物品的个数是固定的,也就是说我们要用的个数肯定是小于等于该个数的,如果我们可以把个数拆解,但可以组合成任意的个数,就可以满足条件。那么二进制拆解肯定是可行的了。掌握O(V*∑log n[i])复杂的解法,单调队列解法有余力的同学掌握。

伪代码:  For i=1…N{   For j=0…ceiling of log2(num[i]){    If num[i]<2^j     tmpNum=num[i]-2^(j-1)    else tmpNum=2^j    For v=V…0     Dp[v]=max(Dp[v-v[i]*tmpNum],dp[v]+tmpNum*w[i])   }  }  

例题:http://poj.org/problem?id=1276


[编辑] 混合三种背包问题

题目:当物品的个数可能是1个,或者多个,或者无限制,求最大的价值。

分析:就是说物品个数可能固定,也可能不固定的情况。在每一次更新的时候,按照上述三种去做,就可以得到答案。理解三种的基础上,第四种就可以迎刃而解。

伪代码:将前三种背包的第二层循环进行组合。

例题http://acm.hdu.edu.cn/showproblem.php?pid=3535


[编辑] 二维费用的背包问题

题目:物品不但有空间,而且有花费cost[i],而总的钱的数量是固定的S。

分析:为了方便,我们假设每个物品只有一个,其他情况类似。如果每个物品不仅有占用空间,而且也有花费。这样费用增加了一维,状态对应增加一维,转移对应进行改变即可。Dp[i][j]表示空间为i,花费为j时的最大价值。

伪代码:  For i=1…N   For v=V…0    For s=S…0     Dp[v][s]=max(Dp[v][s],dp[v-v[i]][s-cost[i]]+w[i])   


[编辑] 分组背包问题

题目:有N组物品,第i组有group[i]个物品,第i组第j个物品的空间为v[i][j],价值为w[i][j],每组的物品只能取一个,给定背包的容量为N,求最大的价值。

分析:物品被分为多个组,但是呢,每个组里面的物品只能取一个。对于每个物品,要么取,要么不取。在01背包中,如果两层循环对换则表示,所有物品只有一个,而且只能取一类物品。那么对于多组物品,只需加上最外层加上组就可以了。

伪代码:  For g=1…N   For v=V…0    For i=1…group[g]     Dp[v]=max(dp[v],dp[v-v[g][i]]+w[g][i])  


[编辑] 有依赖的背包

题目:要选一个物品,必须选另一个物品,又可以被其他物品依赖,但是一个物品不会依赖多个物品,而且不会出现循环依赖的情况。

分析:物品间的依赖组成了一个森林,我们先讨论一棵树的情况。我们从叶子开始进行dp,要么选要么不选,dp值表示该容量下的最大价值,这就是一个树形dp。最后一棵树就可以看成许多个不同容量,不同价值的互斥的一个物品组,多棵树就回到了第六讲的情况。



[编辑] 泛化物品

题目:物品的价值随分配的容量变化而变化,这样对于一个物品,它的消耗和价值不一定再是线性关系。

分析:对于每个物品,我们抽象为V个物品,V为背包的容量,每个物品都有一个价值,这样就编程了分组背包问题。问题迎刃而解。从这个问题,可以看出,复杂背包基本上都可以转变为基本背包问题,理解最基本背包问题是根本。


[编辑] 背包问题问法的变化

背包问题,有很多种问法。比如说恰好多少个物品,也就是费用增加个数这一维,费用由很多种问法。还有可能要求输入方案,或者按字典序最小的方案,这都是可能的。只要能理解了背包问题的本质,只需要少许变形即可得到答案。


[编辑] 总结

总结,背包问题是动态规划很经典的一个问题,在学习的过程中,要仔细体会其中用到的动态规划思想。而且在学习的过程,要学会灵活运用几种背包问题,解决混合的背包问题。背包问题有的时候会和其他的题目进行结合,例如在树形dp中,每次更新要用背包去更新,这样增加了背包问题的隐蔽性。所以,理解并掌握最基本的几种背包,加上灵活应用,就可以应对几乎所有的背包题目。


4.2 树形动态规划

知识讲解

动态动态规划,又称树形dp,顾名思义,就是在树上去做动态规划。理解起来比较容易,一棵树,从跟到叶子动态规划或者从叶子到根动态规划,有的两者都需要,而且满足最优子结构。这类动态规划,也是从状态表示,状态转移来做文章。找到合适的状态和合适的转移,用合适的复杂度去解决问题。


[编辑] 例题分析一

例题分析(http://poj.org/problem?id=1947)

题目大意:给一棵节点数为n的树,求最少去掉几条边可以得到一棵节点数为m的子树。n不大于150。

题目分析: 解答树形dp题目的思路,一般都是从根开始,递归进行处理,到叶子后返回。我们要得到一棵节点数为m的子树,而要求去掉最少的边数。那么我们不妨这样设计状态,dp[i][j]表示以i为根的子树形成一个节点数为j需要切割多少边。对于叶子节点,只有一种情况,即子树节点为1,而所需删除边的个数为0。对于非叶子节点,对于每一个儿子,遍历它的可以形成的子树大小,增加到每一个该节点可形成子树的大小上。对于答案,如果节点不是根,需要将该节点与父亲节点分开,即答案加1。

经验之谈: 树形dp有很多经典题目,自认为每一种都要做。树形dp一般都需要写递归,对代码能力是一个历练。


[编辑] 例题分析二

题目大意:给一个节点数为n(n<=200000)的树,从树的节点中选取3个点X,Y,Z,X到Y的距离不大于X到Z的距离,求X到Y和Y到Z距离之和的最大值。分析:节点数很大,显然n^2的枚举是不可行的,只有O(n)的算法才可行。那么我们尝试去枚举X,Y,Z中的任何一个点,它们都不会对另外两个点同时产生影响,必须再枚举,所以是不可行的。我们想一下X到Y,Y到Z的路径,有一个中间节点A,而距离可以转化为2|AY|+|AX|+|AZ|,其中|AY|<=|AZ|,这样为了求这个最大值,只需求A点到其他点3个最大值,而且,X,Y,Z不在A的同一棵子树上,其中X点可以与A点重合。

这样的话,我们就拿随便一个点为根,求出每个节点到子树叶子节点的最大的三个值,这样就可以处理每个节点为A时,X,Y,Z都为A的后代的情况。然后开始处理有一个不为A的后代节点时的情况。对于这种情况,我们只需在深度遍历时,再取父亲节点的最大距离加上父亲到自己的距离,但是这个最大距离可能就在A这棵子树上得到的,所以要记录最大值是从哪一个子树上得到的,这样的话,当每次去取父亲节点最大值时,如果是A这棵字数上取到的,则取次大值,否则取最大值。[5]

题目到这里就可以得到答案。从上述题目可以看出:题目需要找到一个切入点,例如题目中A点的选择;找到切入点之后,转换一个可解的模型;最后两边遍历,在遍历的过程中,需要用到前面的结果。不得不说,这道题目需要我们发挥我们的创造思维才能解决,当然两边遍历并记录最大值来源的这种方法和技巧,需要我们有经验去支撑。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值