钢条切割问题 DP

钢条切割问题

给定一段长度为n的钢条和一个价格表pi(i=1,2,…,n),求切割钢条方案,是的销售收益rn最大。

方法一:

长度为n的钢条共有2n-1种不同的切割方案,因为在距离钢条左端i(i=1, 2, …, n-1)处,我们总是可以选择切割或者不切割。
一般的,钢条切割收益可以如下表示:
rn = max(pn, r1 + rn-1, r2 + rn-2, …, rn-1 + r1)
第一个参数pn对应不切割,直接售出长度为n的钢条。其余n-1个参数对应另外n-1种方案:对每个i = 1,2,…,n-1,首先将钢条切割成长度为i和n-i的两端,接着求解这两段的最优切割收益ri和rn-i(每周弄方案的最有收益为这两段的最有收益之和)。由于无法预知哪种方案会获得最优收益,我们必须考察所有可能的i,选取其中收益最大者。
为了求解规模为n的原问题,我们先求解形式完全一样,但规模更小的子问题。即当完成首次切割之后,我们将两端钢条看成两个独立的钢条切割问题。通过组合两个相同=关子问题的最优解,并在所有可能的两端切割方案中选取组合收益最大者,构成原问题的最优解

方法二:

固定一端,只对另一端进行递归
将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割(递归求解),对左边的一段则不再进行切割。即问题分解的方式为:将长度为n 的钢条分解为左边开始一段以及剩余部分继续分解的结果,所以可能简化为
rn = max(pi + rn-1) (1 <= i <= n)
自顶向下递归实现

int CutRod(int *p, int n) {
	if (n == 0)
		return 0;
	int q = -INF;
	for (int i = 1; i <= n; i++) {
		q = max(q, p[i] + CutRod(p, n - i));
	}
	return q;
}

但此方法会反复调用相同参数值对自身进行递归调用,即它反复求解相同的子问题
时间复杂度为:T(n) = O(2n)

方法三:带备忘的自顶向下法

仍按照递归的形式编写过程,但过程会保存每个子问题的解,当需要一个子问题的解时,首先检查是否已经保存过此解,如果是,则直接返回保存的值,否则,按照通常的方式计算这个子问题

int MemoizedCutRod(int *p, int n) {
	int r[maxn];
	for (int i = 0; i <= n; i++)
		r[i] = -INF;
	return MemoizedCutRodAux(p, n, r);
}

int MemoizedCutRodAux(int *p, int n, int *r) {
	if (r[n] >= 0)		//如果已经计算过,则直接返回
		return r[n];
	int q;
	if (n == 0)
		return 0;
	else {
		//若没有计算过,则按照通常的方式进行计算
		q = -INF;
		for (int i = 1; i <= n; i++)
			q = max(q, p[i] + MemoizedCutRodAux(p, n - i, r));
	}
	r[n] = q;			//将计算结果保存在数组中
	return q;
}
方法四:自底向上法

恰当的定义子问题的规模,使得任何子问题的求解斗志依赖于更小的子问题的求解。因而我们可以将子问题按规模排序,按由小到大的顺序进行求解。
当求解某个子问题时,它所依赖的那些更小的子问题都已经求解完毕,结果已经保存。每个子问题只求解一次,当我们求解它(也是第一次遇到它)时,它的所有前提子问题都已经求解完毕

int BottomUpCutRod(int *p, int n) {
	int r[maxn];
	r[0] = 0;
	int q;
	for (int j = 1; j <= n; j++) {
		q = -INF;
		for (int i = 1; i < j; i++)
			q = max(q, p[i] + r[j - i]);
		r[j] = q;
	}
	return r[n];
}

5-8行按照升序一次求解每个规模为j的子问题。求解规模为j的子问题方法与CutRod所采用的方法相同,只是直接访问数组r[j-i]来获得规模为j-i的子问题的解,而不必进行递归调用
将规模为j的子问题的解存入r[j]
最后返回r[n],即最优解rn

自底向上与自顶向下算法时间复杂度均为O(n2)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值