士兵过河
题目描述
一只N个士兵的军队正在趁夜色逃亡,途中遇到一条湍急的大河。
敌军在T的时长后到达河面,没到过对岸的士兵都会被消灭。
现在军队只找到了一只小船,这船最多能同时坐上2个士兵。
1)当一个士兵划船过河,用时为a[i];0 <= i < N
2)当两个士兵坐船同时划船过河时,用时为 max(a[j], a[i]) 两士兵中用时最长的。
3)当两个士兵坐船一个士兵划船时,用时为 a[i] * 10 ; a[i] 为划船士兵用时。
4)如果士兵下河游泳,则会被湍急水流直接带走,算作死亡。
请帮忙给出一种解决方案,保证存活的士兵最多,且过河用时最短。
输入描述
第一行:N 表示士兵数(0 < N < 1,000,000)
第二行:T 表示敌军到达时长(0 < T < 100,000,000)
第三行:a[0] a[1] … a[i] … a[N - 1]
a[i] 表示每个士兵的过河时长。
(10 < a[i] < 100; 0 < i < N)
输出描述
第一行:“最多存活士兵数” “最短用时”
测试用例
用例1
输入
5
43
12 13 15 20 50
输出
3 40
说明
可以达到或小于43的一种方案:
第一步:a[0] a[1] 过河用时: 13
第二步:a[0] 返回用时: 12
第三步:a[0] a[2] 过河用时: 15
用例2
输入
5
130
50 12 13 15 20
输出
5 128
说明
可以达到或小于130的一种方案:
第一步:a[1] a[2] 过河用时: 13
第二步:a[1] 返回用时: 12
第三步:a[0] a[4] 过河用时: 50
第四步:a[2] 返回用时: 13
第五步:a[1] a[2] 过河用时: 13
第六步:a[1] 返回用时: 12
第七步:a[1] a[3] 过河用时: 15
用例3
输入
7
171
25 12 13 15 20 35 20
输出
7 171
说明
可以达到或小于171的一种方案:
第一步:a[1] a[2] 过河用时: 13
第二步:a[1] 返回用时: 12
第三步:a[0] a[5] 过河用时: 35
第四步:a[2] 返回用时: 13
第五步:a[1] a[2] 过河用时: 13
第六步:a[1] 返回用时: 12
第七步:a[4] a[6] 过河用时: 20
第八步:a[2] 返回用时: 13
第九步:a[1] a[3] 过河用时: 15
第步:a[1] 返回用时: 12
第步:a[1] a[2] 过河用时: 13
备注
1)两个士兵同时划船时,如果划速不同会导致船原地转圈圈;所以为保持两个士兵划速相同,则需要向划的慢的士兵看齐。
2)两个士兵坐船时,重量增加吃水加深,水的阻力增大;同样的力量划船速度会变慢。
3)由于河水湍急大量的力用来抵消水流的阻力,所以 2)中过河时间不是 a[i] * 2,而是 a[i] * 10。
解题思路
- 小船容量 2 人;
- 两人过河最短过河时间为 a[i] * 10 与 max(a[i], a[j]) 中的最小值;
- 需要士兵存活最多,可根据士兵渡河时间由小到大排序,若排序后过河时间短的士兵无法存活,则时间长的士兵可以不考虑存活;
- 要求士兵总过河时间最短;
就士兵 a[i] 进行过河假设,若需要 a[i] 渡河,则 a[i - 1] 可能已过河或待过河(小船容量为 2,且按过河时间由小到大排序),同时 小船、a[i - 2]及之前的士兵在对岸(如果小船不在对岸,需要有人将小船送回);
那么士兵 a[i] 渡河后总时间就归于两种情况:
1)a[i - 1] 未渡河,需要 a[0] 将船划回,a[i - 1] a[i] 过河后 a[1] 划回,最终 a[0] a[1] 过河
T i = T i − 2 + a [ 0 ] + m i n ( a [ i − 1 ] ∗ 10 , a [ i ] ) + a [ 1 ] + m i n ( a [ 0 ] ∗ 10 , a [ 1 ] ) T_i = T_{i - 2} + a[0] + min(a[i - 1] * 10, a[i]) + a[1] + min(a[0] * 10, a[1]) Ti=Ti−2+a[0]+min(a[i−1]∗10,a[i])+a[1]+min(a[0]∗10,a[1])
2)a[i - 1] 已渡河,需要 a[0] 将船划回,a[0] a[i] 过河
T i = T i − 1 + a [ 0 ] + m i n ( a [ 0 ] ∗ 10 , a [ i ] ) T_i = T_{i - 1} + a[0] + min(a[0] * 10, a[i]) Ti=Ti−1+a[0]+min(a[0]∗10,a[i])
示意图如下
代码示例
/**
* 获取士兵过河最大存活人数及最短过河时间
* @param n 士兵数 (0 < N < 1,000,000)
* @param t 敌军到达时长 (0 < T < 100,000,000)
* @param a a[i]表示每个士兵的过河时长。 (10 < a[i] < 100; 0<= i < N)
* @return 存活士兵数 过河时间
*/
public String crossTheRiver(int n, int t, int[] a) {
Arrays.sort(a);
if(a[0] > t) {
return "0 0";
}
// 士兵数小于 2
if(n < 2) {
return "1 " + a[0];
}
int[] dp = new int[n];
dp[0] = a[0];
dp[1] = minTime(a[0], a[1]);
if(dp[1] > t) {
return "1 " + dp[0];
}
for (int i = 2; i < n; i++) {
int a1 = dp[i - 1] + a[0] + minTime(a[0], a[i]);
int a2 = dp[i - 2] + a[0] + minTime(a[i - 1], a[i]) + a[1] + minTime(a[0], a[1]);
dp[i] = Math.min(a1, a2);
if(dp[i] > t) return i + " " + dp[i - 1];
}
return n + " " + dp[n - 1];
}
/**
* 最短渡河时间
* @param t1 时间1
* @param t2 时间2
* @return 最短时间
*/
private int minTime(int t1, int t2) {
return Math.min(t1 * 10, t2);
}