Programming Challenges 习题11.6.3

PC/UVa:111103/10154

Weights and Measures

给出最多5607只乌龟的重量和力量,求可以将乌龟摞多高。每只乌龟所承受的重量(自己的重量和上面所有乌龟的重量)不能超过它的力量。

通过这道题总结一下做动态规划题目的套路。

第一步,转换为分阶段决策(有的题目不需要明确的转换)。对于这道题,就是要将乌龟逐只加入,每加入一只做一次决策。但是乌龟摞起来后的最终顺序不一定和输入顺序相同,因此需要对输入的乌龟进行排序。

可以根据重量、力量和可承受重量(力量减重量)对乌龟进行排序,因为题目中要保证每只乌龟所承受的重量不能超过乌龟的力量,所以排序的指标应该是力量,或者可承受重量:

  • 假设使用可承受重量进行递增排序。一只乌龟的可承受重量很小,可能是由于重量和力量都很大,导致差值很小,但是这个差值不能反应出乌龟的重量信息。这样排序可能把一个很重,但是力量也很大的乌龟放在上面。对下面的那些较轻,但是力量没有大到很重这个级别的乌龟来说,无论如果也不能撑起来上面的那只乌龟,但是将这两个乌龟交换一个位置也许是合理的
  • 根据上面的分析,排序的指标应该保留重量和力量两方面的信息,也就是按照力量排序,力量相同再按照重量排序。对于相邻的两只乌龟来说,总重量是一样的,力量大一些的在下面,可以保证上面能放更多的乌龟,这个思路和4.6.5 鞋匠的烦恼有些类似,属于贪心排序?

第二步,寻找递推关系。这一步应该满足动态规划的最优子结构无后效性。当乌龟数量较少时,可以通过穷举的方法,将各种合理的排列方式枚举出来,然后选择最高的一个。假设我们已经得到了所有可能的排列方式,现在要根据之前的排序结果,加入一只新的乌龟。这只乌龟可以放在已有排列方式的最上面,也可以放在最下面。如果放在最上面,那么下面每一只乌龟都需要检查力量是否合理,这样就无法将已有的结果作为整体对待,也就是不满足最优子结构性质;还有就是之前摞的顺序会对后面的决策有影响,不满足无后效性。但是如果放在最下面,那么只要这只乌龟的力量可以将自己以及已有的乌龟撑起来就OK,这样满足最优子结构性质,也满足无后效性。

第三步,合并重叠子问题。动态规划另一个性质就是存在重叠子问题,并且动态规划的本质是穷举。假如按照最长上升子序列(LIS)的方法来做,那么就是求当前乌龟在最下面时,能得到的最大高度,但是在这里是不对的。

在LIS问题规模较小时,也是枚举出各种可能的序列,然后将新的数加入到最后,组成新的序列。新的序列是否满足上升性质,只和已有序列的末尾和新的数的大小关系有关,所以只要已有序列的最后一个数相同,那么就是重叠的子问题,可以将它合并。

但是对于乌龟来说,虽然有多种已有排列使得最下面的乌龟是同一只乌龟,但是每种方式的重量可能不同,高度也可能不同,显然在高度相同时,应该保留整体重量最小的,而高度不同时则都应该保留。更一般的,应该不考虑最下面的乌龟具体是哪一只,在高度相同时保留整体重量最小的。

通过以上分析,需要记录的状态应该是i只乌龟时,摞起来后得到不同高度h(范围从0i)时的最小整体重量,递推公式为W(i, h) = min(W(i - 1, h) + W(i - 1, h - 1) + turlte[i].weight),前面一项表示i - 1只乌龟可以达到h的高度,后面表示取前面i - 1只乌龟高度为h - 1的重量最小排列,再加上这只新的乌龟作为最底下的(当然需要满足力量的条件)。例如3只乌龟时,可以得到的高度为0123,在第四只乌龟放在高度为0的下面时,可以得到一个新的高度为1的整体重量,如果这个重量更轻就替换。

操作的本质是将新的乌龟放在每种保留的排列下面,得到一个新的高度增加1的排列,如果这个高度比相同高度(上一轮得到的相同高度)的总重量小,则进行替换,所以可以去掉第一维的变量i,只保留h,公式为W(h) = min(W(h), W(h - 1) + turtle[i].weight)

递推新高度要从最大高度往最小高度循环,从公式中可以看出,如果h从小到大循环,在使用h - 1计算h时,可能会覆盖旧的h值对应的整体重量,这将对h + 1的计算产生影响。

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>

using namespace std;

struct Turtle
{
	int weight, measure;
	int strength;
};

bool operator<(const Turtle &t1, const Turtle &t2)
{
	if (t1.measure != t2.measure) return t1.measure < t2.measure;
	else return t1.weight < t2.weight;
}

void stackTurtle(const vector<Turtle> &vecTurtle)
{
	vector<vector<int>> Weight(vecTurtle.size() + 1, vector<int>(vecTurtle.size() + 1, INT_MAX));
	for (size_t i = 0; i <= vecTurtle.size(); i++)
	{
		Weight[i][0] = 0;
	}
	Weight[1][1] = vecTurtle[0].weight;
	for (size_t curr = 2; curr <= vecTurtle.size(); curr++)
	{
		const Turtle &turtle = vecTurtle[curr - 1];
		Weight[curr] = Weight[curr - 1];
		for (size_t h = curr; h > 0; h--)
		{
			if (Weight[curr - 1][h - 1] != INT_MAX){
				int prevWeight = Weight[curr - 1][h - 1];
				if (prevWeight + turtle.weight <= turtle.measure){
					if (prevWeight + turtle.weight < Weight[curr][h]){
						Weight[curr][h] = prevWeight + turtle.weight;
					}
				}
			}
		}
	}
	int maxHeight = 0;
	for (int h = vecTurtle.size(); h > 1; h--)
	{
		if (Weight[vecTurtle.size()][h] != INT_MAX){
			maxHeight = h;
			break;
		}
	}
	cout << maxHeight << endl;
}

void stackTurtle2(const vector<Turtle> &vecTurtle)
{
	vector<int> Weight(vecTurtle.size() + 1, INT_MAX);
	Weight[0] = 0;
	Weight[1] = vecTurtle[0].weight;
	for (size_t curr = 2; curr <= vecTurtle.size(); curr++)
	{
		const Turtle &turtle = vecTurtle[curr - 1];
		for (size_t h = curr; h > 0; h--)
		{
			if (Weight[h - 1] != INT_MAX){//之前的乌龟可以摞到这个高度
				int prevWeight = Weight[h - 1];
				if (prevWeight + turtle.weight <= turtle.measure){//新的乌龟可以放在下面
					if (prevWeight + turtle.weight < Weight[h]){//同样高度重量更优
						Weight[h] = prevWeight + turtle.weight;
					}
				}
			}
		}
	}
	int maxHeight = 0;
	for (int h = vecTurtle.size(); h >= 1; h--)
	{
		if (Weight[h] != INT_MAX){
			maxHeight = h;
			break;
		}
	}
	cout << maxHeight << endl;
}

int main()
{
	vector<Turtle> vecTurtle;
	Turtle turtle;
	while (cin >> turtle.weight >> turtle.measure){
		turtle.strength = turtle.measure - turtle.weight;
		vecTurtle.push_back(turtle);
	}
	sort(vecTurtle.begin(), vecTurtle.end());
	stackTurtle2(vecTurtle);
	return 0;
}
/*
300 1000
1000 1200
200 600
100 101
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值