[编程题]: 过河问题

过河问题

问题描述:
一群人过河,每个人体重不同,过河需要的时间不同,体重多少,过河时间就要多少, T[1], T[2], … , T[n],已经按从小到大排好序。只有一只船,船每次最多只能载2个人,这两个人过河的最短时间取决于体重最重的那个人,2个人过河之后需要一个人将船划回来供后面的人用。问:所有人过河,需要的最短时间时多少?
输入:人数n,接下来一行是n个人的体重
示例

输入
4
2 10 12 11
输出
39

输入
4
2 7 3 8
输出
19

这个题乍看是让过河时间最短的那个人来回的和其他人过河,因为当他和别人过河之后再将船划回来所需的时间最短。
这个推论看起来是对的,但是其实是有问题的:
比如,有4个人过河,体重(即过河时间)分别为2,3, 7, 8

可以看到这种貌似正确的方法所花的时间为:
3 + 2 + 8 + 2 + 7 = 22

再看另外一种过河方式:

这种过河方式所花费的时间是:
3 + 2 + 8 + 3 + 3 = 19

比上面那种貌似正确的方式用时更短。

这是怎么造成的呢?
可以看到在第一种方法中第2次过河的是 (2, 7),第3次过河的是(2, 8);第二种方法第2次过河的是(7, 8),第3次过河的是(2, 3)。
第二种方法中,8花了8分钟过河,第一种中8过河也花了8分钟过河;而第二种中7跟着8过河,没有单独过河,这中间就节省了很多时间。虽然(7, 8)过河后要3将船划回对岸花的时间比2将船划回对岸花的时间更长,但是第3次过河时,(2, 3)过河花的时间比(2, 7)少得更多,节约的时间为:7 - 3 - 1 = 3 分钟,方案二比方案一少花3分钟。(有点像田忌赛马的思想一样,(7, 8)一起过河比(2, 8)一起过河能够抵消掉更多的时间) 。

既然不是那种貌似最优的方法得到了最优的结果,那应该怎么分析得到求最短时间的方法呢?肯定是超能的Dynamic Programming了.

设dp[i]表示前i个人过河所花费的最短时间,考虑如下情景:
① 当河边还剩下 1 个人时,即dp[i - 1]已经得到
由河对岸过河时间最短的T[1]将船划回来,然后T[i]和T[1]一起过河,因此过河时间为
dp[i] = dp[i - 1] + T[1] + T[i];
② 当河边还剩下 2 个人时,即dp[i - 2]已经得到
由河对岸过河时间最短的T[1]将船划回来;
设河边剩下的两个人为 j、i,T[j] < T[i];

  • 若下一次 j 和 i不一起过河,而是T[1]和T[j]一起过河得到的整体的过河时间最短,将是上面的那种情况;
    dp[j] = dp[i - 1] = dp[i - 2] + T[1] + T[j];
    dp[i] = dp[i - 1] + T[1] + T[i] = dp[j] + T[1] + T[i]
  • 若下一次 j 和 i 一起过河,然后再由河对岸过河时间最短的人T[2]将船划回,然后T[1]和T[2]一起过河;
    dp[i] = dp[i - 2] + T[1] + T[i] + T[2] + T[2];

由此可的

dp[i] = max(dp[i - 1] + T[1] + T[i], dp[i - 2] + T[1] + T[i] + 2 * T[2])
边界:
dp[1] = T[1]
dp[2] = T[2]

那你只考虑了剩2个人得出了递推公式(也即转移方程),那河边剩下3个人没有过河呢,那有没有可能dp[i]取决于dp[i - 3]呢?
这是不会的,因为船每次只能载2个人,dp[i]的状态最多和dp[i - 2]有关。举例说明的话如下:

举例说明:
根据上面的分析,岸边还剩下2个人的时候,产生的过河组合有:(1, j)(1, i)和(j, i)(1, 2);
假设岸边还剩下3个人:k、j、i,且T[k] < T[j] < T[i],和对岸过河时间最短的 1 将船划回来了,那么岸边有4个人:1、k、j、i,可以分为6中情况:
①1, k先过河,花费时间:
T[k] + T[1] + T[j] + T[1] + T[i]
或者
T[k] + T[1] + T[i] + T[2] + T[2]
②1, j先过河,花费时间:
T[j] + T[1] + T[k] + T[1] + T[i]
或者
T[j] + T[1] + T[i] + T[2] + T[2]
③1, i先过河,花费时间:
T[i] + T[1] + T[k] + T[1] + T[j]
或者
T[i] + T[1] + T[j] + T[2] + T[2]
④k, j先过河,花费时间:
T[j] + T[2] + T[i] + T[1] + T[2]
⑤k, i先过河,花费时间:
T[i] + T[2] + T[j] + T[1] + T[2]
⑥j, i先过河,花费时间:
T[i] + T[2] + T[k] + T[1] + T[2]

消减去相同量,共得到3种结果:
T[k] + T[j] + T[1]
T[k] + T[2] + T[2]
T[j] + T[2] + T[2]
而T[k] < T[k],所以最优结果一定在 T[k] + T[j] + T[1] 和 T[k] + T[2] + T[2]中,而情况①中的去掉相同项得到的就是这两个,因此只考虑情况①就能得到全局最优解了,其他组合花费的时间不可能比情况①花费的时间更小。
而情况①中,可以看到是1, k先过河,再j、i 过河,因此:
dp[i - 3] + ① 就相当于dp[i - 2] + (j、i 的情况)
因此,求dp[i]时不需要考虑dp[i - 3],即dp[i]的状态只与dp[i - 1]和dp[i - 2]有关

代码

Java

public class Main{
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int T = input.nextInt();
        for (int k = 0; k < T; k++) {
            int n = input.nextInt();
            int[] people = new int[n + 1];
            for (int j = 1; j <= n; j++) people[j] = input.nextInt();
            if (n == 2) System.out.println(people[1] + people[2]);
            Arrays.sort(people);
            int[] dp = new int[n + 1];
            dp[1] = people[1];
            dp[2] = people[2];

            for (int i = 3; i <= n; i++) {
                dp[i] = Math.min(dp[i - 1] + people[1] + people[i], dp[i - 2] + people[1] + people[i] + 2 * people[2]);
            }
            System.out.println(dp[n]);
        }
    }
}
输出
4
2 10 12 11
37

4
2 7 3 8
19

C++

#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;

int main() {
	int n;
	cin >> n;
	vector<int> dp(n + 1, 0);
	vector<int> T(n + 1, 0);
	for (int i = 0; i < n; i++) cin >> T[i];
	sort(T.begin(), T.end());
	dp[1] = T[1];
	dp[2] = T[2];
	for (int i = 3; i <= n; i++) {
		dp[i] = min(dp[i - 1] + T[1] + T[i], dp[i - 2] + T[1] + T[i] + T[2] * 2);
	}
	cout << dp[n] << endl;
	return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值