题目描述
一支N个士兵的军队正在趁夜色逃亡,途中遇到一条湍急的大河。
敌军在T的时长后到达河面,没到过对岸的士兵都会被消灭。
现在军队只找到了1只小船,这船最多能同时坐上2个士兵。
- 当1个士兵划船过河,用时为 a[i];0 <= i < N
- 当2个士兵坐船同时划船过河时,用时为max(a[j],a[i])两士兵中用时最长的。
- 当2个士兵坐船1个士兵划船时,用时为 a[i]*10;a[i]为划船士兵用时。
- 如果士兵下河游泳,则会被湍急水流直接带走,算作死亡。
请帮忙给出一种解决方案,保证存活的士兵最多,且过河用时最短。
输入描述:
第一行: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)
输出描述:
第一行:”最多存活士兵数” “最短用时”
备注:
- 两个士兵的同时划船时,如果划速不同则会导致船原地转圈圈;所以为保持两个士兵划速相同,则需要向划的慢的士兵看齐。
- 两个士兵坐船时,重量增加吃水加深,水的阻力增大;同样的力量划船速度会变慢;
- 由于河水湍急大量的力用来抵消水流的阻力,所以2)中过河用时不是a[i] *2,
而是a[i] * 10。
示例1 输入输出示例仅供调试,后台判题数据一般不包含示例
用例
输入 | 5 43 12 13 15 20 50 |
---|---|
输出 | 3 40 |
说明 | 可以达到或小于43的一种方案: 第一步:a[0] a[1] 过河用时:13 第二步:a[0] 返回用时:12 第三步:a[0] a[2] 过河用时:15 |
输入 | 5 130 50 12 13 15 20 |
---|---|
输出 | 5 128 |
说明 | 可以达到或小于130的一种方案: 第一步:a[1] a[2] 过河用时:13 第二步:a[1] 返回用时:12 第三步:a[0] a[5] 过河用时:50 第四步:a[2] 返回用时:13 第五步:a[1] a[2] 过河用时:13 第六步:a[1] 返回用时:12 第七步:a[1] a[3] 过河用时:15 所以输出为: 5 128 |
输入 | 7 171 25 12 13 15 20 35 20 |
---|---|
输出 | 7 171 |
说明 | 可以达到或小于60的一种方案: 第一步: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 所以输出为: 7 171 |
题目解析
本题是 POJ - 1700 Crossing River_伏城之外的博客CSDN博客 的变种题。
建议大家先搞定这题,然后再来看本题
本题在前面这题的基础上,多了一个过河时间限制以及要求最多存活士兵(即在限制时间内过最多的
人
这里我们完全可以用 二分法,在O~N中尝试找到成功过河的人数,其中0指的是成功过河的人数为0个,N指的是成功过河的人数为N个。
将二分法找到的可能人数mid带入上面 POJQ-1700的逻辑中,计算出mid个人都过河所雪的最短时间need,将need和本题过河时间限制limit进行比较:
- 若 need >limit,则说明当前mid个人无法成功过河,即过河人数偏多了,我们应该减少过河人数
- 若 need<limit,则说明当前mid个人可以成功过河,但是可能还可以过更多人数
- 若 need == limit,则说明当前mid个人刚刚好可以在limit时间过完河,则此时mid就是最多存货的士兵数
另外,本题中说:
当2个士兵坐船1个士兵划船时,用时为 a[i] * 10; a[i]为划船士兵用时。
假设x士兵划船用时为a[x], y士兵划船用时为a[y], a[x] < a[y]
这句话的意思是: 如果x,y一起划船,有两种过河时间,分别是:
- a[x] * 10
- a[y]
如果a[y] > a[x] * 10,我们应该选择a[x] * 10,即让较快的士兵单独划船过河,这样耗时更短。
但是,本题中又说:
(10 < a[i] < 100; 0 <= i < N)
即
- 10 < a[y] < 100
- 10 < a[x] < 100
那么必然: 100 < a[x] * 10 < 1000
即必然 a[x] * 10 > a[y]
因此,我们不需要考虑上面那种两个士兵坐船,一个士兵划船的情况。
java解法一:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class GoThroughTheRiver {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int t = sc.nextInt();
int[] times = new int[n];
for (int i = 0; i < n; i++) {
times[i] = sc.nextInt();
}
System.out.println(getResult(n, t, times));
}
public static String getResult(int n, int t, int[] times) {
Arrays.sort(times);
int[] dp = new int[n];
dp[0] = times[0];
if (dp[0] > t) return "0 0";
dp[1] = getMax(times[0], times[1]);
if (dp[1] > t) return 1 + " " + dp[0];
for (int i = 2; i < n; i++) {
dp[i] = Math.min(dp[i - 1] + times[0] + getMax(times[0], times[i]),
dp[i - 2] + times[0] + getMax(times[i - 1], times[i]) + times[1] + getMax(times[0], times[1]));
if (dp[i] > t) return i + " " + dp[i - 1];
}
return n + " " + dp[n - 1];
}
public static int getMax(int t1, int t2) {
return Math.min(t1 * 10, t2);
}
}
java解法二:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class GoThroughTheRiver {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int T = sc.nextInt();
List<Integer> list = new ArrayList<>();
for (int i = 0; i < N; i++) {
list.add(sc.nextInt());
}
boolean isLeft = true; //是否从左侧士兵开始过河
boolean isRight = true; //右侧士兵是否满足过河条件(排序后右侧士兵耗时长)
int time = 0; //过河时间
int count = 0; //过河士兵数量
int returnTime = 0; //最后一人的返程时间
List<Integer> duian = new ArrayList<>(); //对岸的士兵集合
while (list.size() != 0) {
Collections.sort(list); //对士兵进行排序
int a, b; //坐船的两个士兵
if (isLeft) { //左侧士兵开始过河或者右侧士兵无法过河
a = list.get(0); //左侧第一个士兵
b = list.get(1); //左侧第二个士兵
list.remove(0); //过河的士兵需要移除
list.remove(0);
} else {
a = list.get(list.size() - 2); //右侧第一个士兵(倒数第二个士兵)
b = list.get(list.size() - 1); //右侧第二个士兵(倒数第一个士兵)
list.remove(list.size() - 1);
list.remove(list.size() - 1);
}
count += 2;
int tempTime; //当前两个士兵的过河最优时间
if (a > b) {
tempTime = Math.min(a, b * 10); //找出两人划船的最优方案
} else {
tempTime = Math.min(b, a * 10);
}
if (time + tempTime >= T) { //此时时间已经超时
if (isLeft) { //如果是左侧的士兵则表示时间已经不够了(因为右侧士兵的耗时更长)
if (time + tempTime == T) { //时间刚刚好
time += tempTime;
} else {
count--; //时间超了需要将此次过河的人剔除
time -= returnTime; //上一次返程时间也取消了
}
break; //跳出循环,表示之后不能有士兵过河了
} else {
count -= 2; //过河人数还原
isRight = false; //右侧不再满足过河的时间
isLeft = true;
list.add(a); //过河士兵返回岸边
list.add(b);
}
} else {
time += tempTime; //到此刻士兵过河所花的时间和
if (list.size() == 0) { //士兵已经全部过河直接跳出循环
break;
}
duian.add(a); //对岸的士兵集合添加两个已经过河的士兵
duian.add(b);
Collections.sort(duian); //求出对岸滑的最快的士兵,让他返程
returnTime = duian.get(0);
time += returnTime; //最快的士兵返程所花时间也要加上
if (time >= T) { //此时时间已经超时
time -= duian.get(0); //减去返程所花的时间
if (isLeft) {
break; //如果是左侧士兵则表示之后没有士兵能够过河
} else {
time -= tempTime; //右侧士兵则恢复原样,让左侧的士兵再试试
isRight = false; //右侧士兵不能再过河了
isLeft = true;
count -= 2; //过河人数还原
duian.remove(duian.size() - 1); //对岸的士兵返回岸边
duian.remove(duian.size() - 1);
list.add(a);
list.add(b);
}
} else {
list.add(duian.get(0)); //岸边添加返程的士兵
duian.remove(0); //对岸移除返程的士兵
isLeft = !isRight || !isLeft; //如果右侧士兵无法过河则永远从左侧士兵中选
count--; //过河人数减一(因为返程了)
}
}
}
System.out.println(count + " " + time);
}
}
C 解法
#include <stdio.h>
#include <sys/types.h>
#define MIN(a, b) ((a) < (b)) ? (a) : (b)
#define MAX(a, b) ((a) > (b)) ? (a) : (b)
int get_shorter_time(int a, int b) {
if (a * 10 < b) {
return a * 10;
}
return b;
}
int cmpfunc(const void *a, const void *b) {
return (*(int *) a - *(int *) b);
}
int main() {
int N, T;
scanf("%d %d", &N, &T);
int a[N];
for (int i = 0; i < N; i++) {
scanf("%d", &a[i]);
}
qsort(a, N, sizeof(int), cmpfunc);
int dp[N];
//初始状态 0 和 1
dp[0] = a[0];
if (dp[0] > T) {
printf("0 0");
return 0;
}
dp[1] = get_shorter_time(a[0], a[1]);
if (dp[1] > T) {
printf("%d %d", 1, dp[0]);
return 0;
}
//状态转移方程
for (int i = 2; i < N; i++) {
dp[i] = MIN(dp[i - 1] + a[0] + get_shorter_time(a[0], a[i]),
dp[i - 2] + a[0] + get_shorter_time(a[i - 1], a[i]) + a[1] + get_shorter_time(a[0], a[1]));
//耗时超了T立马结束
if (dp[i] > T) {
printf("%d %d", i, dp[i - 1]);
return 0;
}
}
printf("%d %d", N, dp[N - 1]);
return 0;
}
C++解法
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
int getMax(int t1, int t2)
{
return min(t1 * 10, t2);
}
string func(int n, int t, vector<int> ×)
{
sort(times.begin(), times.end());
vector<int> dp(n, 0);
dp[0] = times[0];
if (dp[0] > t)
return "0 0";
dp[1] = getMax(times[0], times[1]);
if (dp[1] > t)
return to_string(1) + " " + to_string(dp[0]);
for (int i = 2; i < n; i++) {
dp[i] = min(dp[i - 1] + times[0] + getMax(times[0], times[i]),
dp[i - 2] + times[0] + getMax(times[i - 1], times[i])
+ times[1] + getMax(times[0], times[1]));
if (dp[i] > t)
return to_string(i) + " " + to_string(dp[i - 1]);
}
return to_string(n) + " " + to_string(dp[n - 1]);
}
void SplitInteger(string input, vector<int> &output, const string &pattern)
{
string::size_type pos;
input += pattern;
for (int i = 0; i < input.size(); i++) {
pos = input.find(pattern, i);
if (pos < input.size()) {
string temp = input.substr(i, pos - i);
if ((temp != pattern) && (!temp.empty())) {
output.push_back(stoi(temp));
}
i = pos + pattern.size() - 1;
}
}
}
int main()
{
string line1;
getline(cin, line1);
string line2;
getline(std::cin, line2);
string line3;
getline(std::cin, line3);
vector<int> output;
SplitInteger(line3, output, " ");
cout << func(stoi(line1), stoi(line2), output) << endl;
return 0;
}
Python解法一:
from typing import List
class Solution:
def get_shorter_time(a, b):
if a * 10 < b:
return a * 10
return b
if __name__ == "__main__":
N = int(input())
T = int(input())
a = [int(x) for x in input().split(" ")]
a = sorted(a)
dp = [0 for x in range(N)]
# 初始状态 0 和 1
dp[0] = a[0]
dp[1] = get_shorter_time(a[0], a[1])
time_flag = True
if dp[0] > T:
print("0 0")
elif dp[1] > T:
print(str(1) + " " + str(dp[0]))
else:
# 状态转移方程
for i in range(2, N):
dp[i] = min(dp[i - 1] + a[0] + get_shorter_time(a[0], a[i]),
dp[i - 2] + a[0] + get_shorter_time(a[i - 1], a[i]) + a[1] + get_shorter_time(a[0], a[1]))
# 耗时超T立马结束
if dp[i] > T:
print(str(i) + " " + str(dp[i - 1]))
time_flag = False
break
if time_flag:
print(str(N) + " " + str(dp[N - 1]))
Python解法二:
from typing import List
class Solution:
def solution(self, N: int, T: int, lis: List[int]) -> None:
# 待实现函数,在此函数中填入你的代码
isLeft = True
isRight = True
time = 0
count = 0
returnTime = 0
duian = []
while len(lis) != 0:
lis.sort()
if isLeft or not isRight:
a = lis[0]
b = lis[1]
lis.remove(lis[0])
lis.remove(lis[0])
else:
a = lis[-2]
b = lis[-1]
lis.pop()
lis.pop()
count += 2
if a > b:
tempTime = min(a, b * 10)
else:
tempTime = min(b, a * 10)
if time + tempTime >= T:
if isLeft:
if time + tempTime == T:
time += tempTime
else:
count -= 1
time -= returnTime
break
else:
count -= 2
isRight = False
isLeft = False
lis.append(a)
lis.append(b)
else:
time += tempTime
if len(lis) == 0:
break
duian.append(a)
duian.append(b)
duian.sort()
returnTime = duian[0]
time += returnTime
if time >= T:
time -= duian[0]
if isLeft:
break
else:
time -= tempTime
isRight = False
isLeft = True
count -= 2
duian.pop()
duian.pop()
lis.append(a)
lis.append(b)
else:
lis.append(duian[0])
duian.remove(duian[0])
isLeft = not isRight or not isLeft
count -= 1
print(str(count) + ' ' + str(time))
if __name__ == "__main__":
N = int(input())
T = int(input())
lis = list(map(int, input().split(' ')))
Solution().solution(N, T, lis)
JavaScript
let strings = readLine().split(",");
let errorSer = readLine().split(",");
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const lines = [];
rl.on("line", (line) => {
lines.push(line);
if (lines.length === 3) {
const N = lines[0] - 0;
const T = lines[1] - 0;
const times = lines[2].split(" ").map(Number);
console.log(getResult(N, T, times));
lines.length = 0;
}
});
/**
*
* @param {*} N 士兵数
* @param {*} T 过河时间上限
* @param {*} times 数组,元素表示每个士兵的过河时长
*/
function getResult(N, T, times) {
// 将士兵过河时间,从小到达升序
times.sort((a, b) => a - b);
// dp[i]表示前i+1个士兵过河所需最少时间
const dp = new Array(N);
for (let i = 0; i < N; i++) {
if (i >= 3) {
dp[i] = Math.min(
dp[i - 1] + times[0] + times[i],
dp[i - 2] + times[0] + 2 * times[1] + times[i]
);
} else if (i === 2) {
dp[2] = times[1] + times[0] + times[2];
} else if (i === 1) {
dp[1] = times[1];
} else {
dp[0] = times[0];
}
if (dp[i] > T) return `${i} ${dp[i - 1] ?? 0}`;
}
return `${N} ${dp[N - 1]}`;
}