【士兵过河】

题目描述

一支N个士兵的军队正在趁夜色逃亡,途中遇到一条湍急的大河。
敌军在T的时长后到达河面,没到过对岸的士兵都会被消灭。
现在军队只找到了1只小船,这船最多能同时坐上2个士兵。

  1. 当1个士兵划船过河,用时为 a[i];0 <= i < N
  2. 当2个士兵坐船同时划船过河时,用时为max(a[j],a[i])两士兵中用时最长的。
  3. 当2个士兵坐船1个士兵划船时,用时为 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. 两个士兵的同时划船时,如果划速不同则会导致船原地转圈圈;所以为保持两个士兵划速相同,则需要向划的慢的士兵看齐。
  2. 两个士兵坐船时,重量增加吃水加深,水的阻力增大;同样的力量划船速度会变慢;
  3. 由于河水湍急大量的力用来抵消水流的阻力,所以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> &times)
{
    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]}`;
}
士兵过河问题是一个经典的数学难题,考查的是回溯算法的应用。问题描述如下:n个士兵要过一条河,河中间有一条船,船最多能载两个人。士兵过河时必须遵守一定的规则,如无一人驾船则不能登岸等。请设计一个算法,使得所有士兵能够顺利过河,并且在满足规则的前提下,时间最短。 使用Python实现解决这个问题,我们可以采用递归的思想。首先,我们定义一个函数f,它的参数是当前已经到达对岸的士兵集合,剩余在这一岸的士兵集合,以及当前对岸船上的人数。在f函数中,我们需要判断当前的状态是否满足规则,如果满足,则继续递归搜索;否则,返回上一级,回溯搜索。 那么如何判断当前状态是否满足规则呢?我们可以定义一个二元组(a,b)表示当前岸上的士兵集合,其中a表示对岸上的士兵集合,b表示这一岸上的士兵集合。另外,我们可以用另一个二元组(x,y)表示船上的士兵数量,其中x表示对岸上的士兵数量,y表示这一岸上的士兵数量。那么,我们可以将所有满足条件的状态存入一个列表中,在递归时,从列表中选择一个状态,作为下一级递归的状态。如果递归成功(即士兵全部过河),则输出结果,结束程序。 因此,使用Python解决士兵过河问题,需要定义一个函数,以确定状态是否满足规则。然后使用递归算法,遍历所有可能的状态,找到一组满足运行要求的状态。在找到解决方案后,递归即可结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值