UVa Problem 10261 Ferry Loading (渡船装载)

本文探讨了一种算法问题——如何有效装载车辆到两个固定长度的渡船上。通过对暴力法、贪心法及动态规划等方法的分析,最终采用动态规划解决了问题,并详细介绍了其背后的逻辑与实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// Ferry Loading (渡船装载) // PC/UVa IDs: 111106/10261, Popularity: B, Success rate: low Level: 3 // Verdict: Accepted // Submission Date: 2011-10-15 // UVa Run Time: 0.536s // // 版权所有(C)2011,邱秋。metaphysis # yeah dot net // // [解题方法] // 使用暴力法在这里显然是行不通的(如果测试数据弱的话,可以一试),但是本题车辆可达两百辆之多,指 // 数级的计算时间够你受的(但是很有意思,使用回溯法竟然可以勉强通过,可见测试数据并不是很强,若 // 使用回溯法,当车辆的长度小于某一车道的空余空间时,可以放置于该条车道,继续回溯,不过使用这种方法 // 一定要有效的记录已经访问过的状态,要不然是无法在规定时间内 AC 的,这可以通过使用 STL 中的 set // 来实现)。 // // 贪心法在这里也不适用,如果使用总是把车辆放到空间比较大的车道里的策略的话,反例如下: // // 50 // 2000 // 3000 // 1000 // 1000 // 1500 // 700 // 800 // // 若先放置在车道 A,放置不下放置在另外一条车道 B,后续车辆如果车身长度小于车道 A 的空余长度,则 // 继续放置在 A 车道,反例如下: // // 6 // 200 // 200 // 400 // 400 // // 动态规划行不行?题目要求用两个固定长度的车道尽可能的装载更多的车辆,车辆的长度和不能超过车道的 // 长度,每个车道的车辆长度和都是一个整数。设车道长为 length,车辆共有 n 辆,则要求最大的登船数 // 量 j(0 <= j <= n),使得 j 辆车能放置到两条长为 length 的车道内。假设当前已登船的车辆数量 // 已经达到了最大数量 j,前 j 辆车的长度和为 sum[j],左侧车道内车辆长度和为 l,若 sum[j] == l, // 则表明这 j 辆车可以全部放置在一个车道里,否则长度为 sum[j] - l 的车辆必须放置在另一个车道内。 // 那么可以设立一个数组记录当登船了 i 辆车辆后,左侧车辆的长度为 m,记为 memo[i][m],右侧的车辆 // 长度为必定为 sum[i] - m,记为 memo[i][sum[i] - m],若都能放置,则都为 true,初始条件为 // memo[0][0] = true,从下往上 DP 求 memo[i][length] = true 时最大可能的 i。 #include <iostream> #include <algorithm> #include <cstring> #include <set> using namespace std; #define MAXN 201 #define MAXL 100001 #define EMPTY (-1) // 车道状态类,current 表示当前已经登船的车辆数,left 表示左侧车道剩余空间,right 表示右侧车道 // 剩余空间数。 class state { public: int current, left, right; state() {} state(int incurrent, int inleft, int inright) { current = incurrent; left = inleft; right = inright; } ~state() { } bool operator<(const state & other) const { return (current == other.current ? (left == other.left ? right < other.right : left < other.left) : current < other.current); } bool operator==(const state & other) const { return (current == other.current && left == other.left && right == other.right); } }; set < state > cache; bool port[MAXN + 1], answer[MAXN + 1]; int cars[MAXN + 1], nCars, maxCars; // 回溯法解题,current 表示当前需要登船的车辆编号,left 表示左侧车道剩余的空间,right 表示右 // 侧车道剩余的空间。 void backtrack(int current, int left, int right) { // 若当前缓存不存在该状态,则将该状态放入缓存。 if (cache.count(state(current, left, right)) > 0) return; cache.insert(state(current, left, right)); // 取第 current 辆车的车身长度。若当前全部车辆都已经登船,则表明车道可容纳所有车辆,若 // 当前车辆的长度大于两个车道的剩余空间,则比较当前登船的数量是否大于当前发现的最大登船 // 车辆数。 int carLength = cars[current]; if (current > nCars || carLength > max(left, right)) { if ((current - 1) > maxCars) { maxCars = current - 1; memcpy(answer, port, sizeof(port)); } return; } // 回溯。 if (carLength <= left) { port[current] = true; backtrack(current + 1, left - carLength, right); } if (carLength <= right) { port[current] = false; backtrack(current + 1, left, right - carLength); } } void solve_by_backtrack(void) { int cases, ferryLength, carLength; bool printBoredBlankLine = false; cin >> cases; while (cases--) { if (printBoredBlankLine) cout << endl; else printBoredBlankLine = true; cin >> ferryLength; ferryLength *= 100; nCars = 1; while (cin >> carLength, carLength) { if (nCars < MAXN) cars[nCars++] = carLength; } nCars--; maxCars = 0; cache.clear(); backtrack(1, ferryLength, ferryLength); cout << maxCars << endl; for (int i = 1; i <= maxCars; i++) cout << (answer[i] ? "port" : "starboard") << endl; } } bool memo[MAXN + 1][MAXL + 1]; int choice[MAXN + 1][MAXL + 1]; int sumLength[MAXN + 1]; void solve_by_dynamic_programming(void) { int cases, ferryLength, carLength; bool printBoredBlankLine = false; cin >> cases; while (cases--) { if (printBoredBlankLine) cout << endl; else printBoredBlankLine = true; cin >> ferryLength; ferryLength *= 100; nCars = 1; sumLength[0] = 0; while (cin >> carLength, carLength) { if (nCars < MAXN) { cars[nCars] = carLength; sumLength[nCars] = sumLength[nCars - 1] + carLength; nCars++; } } nCars--; pair < int, int > longest; memset(memo, false, sizeof(memo)); memset(choice, EMPTY, sizeof(choice)); memo[0][0] = true; for (int i = 0; i < nCars; i++) for (int j = 0; j <= ferryLength; j++) if (memo[i][j]) { if ((j + cars[i + 1]) <= ferryLength) { memo[i + 1][j + cars[i + 1]] = true; choice[i + 1][j + cars[i + 1]] = 1; longest = make_pair(i + 1, j + cars[i + 1]); } if ((sumLength[i] - j + cars[i + 1]) <= ferryLength) { memo[i + 1][j] = true; choice[i + 1][j] = 0; longest = make_pair(i + 1, j); } } cout << longest.first << endl; string solution; for (int i = longest.first, j = longest.second; i > 0 && choice[i][j] != EMPTY; i--) { solution = (choice[i][j] ? "port\n" : "starboard\n" ) + solution; if (choice[i][j]) j -= cars[i]; } cout << solution; } } int main(int ac, char *av[]) { // 回溯法,差强人意,UVa RT:2.404s。 // solve_by_backtrack(); // 动态规划,UVa RT:0.536s。 solve_by_dynamic_programming(); return 0; }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值