手电筒过桥问题变种

31 阅读5分钟

031.巧过关卡

第二次世界大战爆发后,德军对犹太人的迫害达到极点。犹太人乔安娜那时六岁,一家人想要逃出柏林,她爸爸托人拿到了一张通行证。一家四人来到了位于柏林城外一个独木桥上的关卡处,上面贴了告示,规定:一个通行证最多可以带两人出入,但不记名,也可重复使用。爸爸算了一下:爸爸单独走过独木桥需要2分钟,妈妈需要4分钟,乔安娜需要8分钟,奶奶需要10分钟。每次两个人出关卡,还需要有人把通行证拿回来。但是还有24分钟,城里的追兵就要追上来了,他们能逃脱吗?

贪心+DP算法

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

int main() {
    int n;
    cout << "请输入人数: ";
    cin >> n;

    vector<int> times(n);
    cout << "请输入每个人过桥所需时间: ";
    for (int i = 0; i < n; ++i) {
        cin >> times[i];
    }

    // 特殊情况处理
    if (n == 0) {
        cout << 0 << endl;
        return 0;
    }
    if (n == 1) {
        cout << times[0] << endl;
        return 0;
    }

    // 从小到大排序
    sort(times.begin(), times.end());

    // dp[i] 表示前 i+1 个人(索引 0~i)全部过桥的最短时间
    vector<long long> dp(n, 0);

    dp[0] = times[0];                // 1人
    dp[1] = times[1];                // 2人:一起过

    if (n >= 3) {
        dp[2] = times[0] + times[1] + times[2]; // 3人:最快来回送
    }

    // 动态规划:从第4人开始(索引3)
    for (int i = 3; i < n; i++) {
        // 方案1:最快的人带第i人过去(来回一次)
        long long plan1 = dp[i - 1] + times[0] + times[i];

        // 方案2:最快和次快先过去,最快回来;最慢和次慢过去,次快回来
        long long plan2 = dp[i - 2] + times[0] + 2 * times[1] + times[i];

        dp[i] = min(plan1, plan2);
    }

    cout << "所有人过桥的最短时间为: " << dp[n - 1] << " 分钟" << endl;
    return 0;
}

请输入人数: 4
请输入每个人过桥所需时间: 1 2 5 10                  
所有人过桥的最短时间为: 17 分钟

通用问题背景

· 有n个人要在夜晚过桥

· 每次最多2人一起过桥

· 只有一盏灯(需要有人带回,本题为通行证)

· 每个人过桥时间不同

· 求所有人过桥的最短时间

算法思路

  1. 排序 先将所有人按过桥时间从小到大排序:

· times[0] 是最快的人

· times[1] 是次快的人

· times[n-1] 是最慢的人

  1. 基础情况 · n=0:0分钟

· n=1:只需自己过桥,times[0]分钟

· n=2:两人一起过,取较慢者的时间 times[1]

· n=3:最快的人来回送灯,时间为 times[0]+times[1]+times[2]

  1. 动态规划核心(n≥4) 对于第i个人(从索引3开始),分情况贪心,有两种最优策略:

方案1:最快的人接送

· 最快的人带第i人过去:times[i]

· 最快的人单独回来:times[0]

· 总时间:dp[i-1] + times[0] + times[i] 方案2:利用次快的人

· 最快和次快过去:times[1]

· 最快回来:times[0]

· 最慢和次慢过去:times[i]

· 次快回来:times[1]

· 总时间:dp[i-2] + times[0] + 2*times[1] + times[i]

  1. 时间复杂度

· 排序:O(n log n) · 动态规划:O(n) · 总复杂度:O(n log n)

此题最终解决

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

// 存储每个人的信息
struct Person {
    string name;
    int time;
 
    Person(string n, int t) : name(n), time(t) {}
    
    // 用于排序的比较函数
    bool operator<(const Person& other) const {
        return time < other.time;
    }
};

// 存储每一步的信息
struct Step {
    string person1;
    string person2;
    string direction; // "→" 或 "←"
    int time;
};

// 动态规划求解最小过桥时间
pair<long long, vector<int>> solveBridgeCrossing(const vector<Person>& people) {
    int n = people.size();
    
    // 提取排序后的时间和名字
    vector<int> sorted_times;
    vector<string> sorted_names;
    for (auto& p : people) {
        sorted_times.push_back(p.time);
        sorted_names.push_back(p.name);
    }
    
    // DP数组和选择记录
    vector<long long> dp(n, 0);
    vector<int> choice(n, 0); // 0: 初始, 1: 方案1, 2: 方案2
    
    // 初始状态
    if (n >= 1) {
        dp[0] = sorted_times[0];
        choice[0] = 0;
    }
    if (n >= 2) {
        dp[1] = sorted_times[1];
        choice[1] = 0;
    }
    if (n >= 3) {
        dp[2] = sorted_times[0] + sorted_times[1] + sorted_times[2];
        choice[2] = 0;
    }
    
    // 动态规划
    for (int i = 3; i < n; i++) {
        long long plan1 = dp[i-1] + sorted_times[0] + sorted_times[i];
        long long plan2 = dp[i-2] + sorted_times[0] + 2*sorted_times[1] + sorted_times[i];
        
        if (plan1 <= plan2) {
            dp[i] = plan1;
            choice[i] = 1;
        } else {
            dp[i] = plan2;
            choice[i] = 2;
        }
    }
    
    return {dp[n-1], choice};
}

// 回溯构建过桥步骤
vector<Step> buildSteps(const vector<Person>& people, const vector<int>& choice) {
    int n = people.size();
    vector<Step> steps;
    
    // 提取排序后的时间和名字
    vector<int> sorted_times;
    vector<string> sorted_names;
    for (auto& p : people) {
        sorted_times.push_back(p.time);
        sorted_names.push_back(p.name);
    }
    
    int idx = n - 1;
    
    while (idx >= 0) {
        if (idx == 0) {
            // 只剩一个人
            steps.push_back({sorted_names[0], "", "→", sorted_times[0]});
            break;
        } else if (idx == 1) {
            // 剩两个人
            steps.push_back({sorted_names[0], sorted_names[1], "→", sorted_times[1]});
            break;
        } else if (idx == 2) {
            // 剩三个人
            steps.push_back({sorted_names[0], sorted_names[1], "→", sorted_times[1]});
            steps.push_back({sorted_names[0], "", "←", sorted_times[0]});
            steps.push_back({sorted_names[0], sorted_names[2], "→", sorted_times[2]});
            break;
        } else {
            if (choice[idx] == 1) {
                // 方案1:最快带最慢
                steps.push_back({sorted_names[0], sorted_names[idx], "→", sorted_times[idx]});
                steps.push_back({sorted_names[0], "", "←", sorted_times[0]});
                idx--;
            } else {
                // 方案2:最快和次快去,最快回;最慢和次慢去,次快回
                steps.push_back({sorted_names[0], sorted_names[1], "→", sorted_times[1]});
                steps.push_back({sorted_names[0], "", "←", sorted_times[0]});
                steps.push_back({sorted_names[idx-1], sorted_names[idx], "→", sorted_times[idx]});
                steps.push_back({sorted_names[1], "", "←", sorted_times[1]});
                idx -= 2;
            }
        }
    }
    
    // 反转步骤(因为我们是从后往前回溯的)
    reverse(steps.begin(), steps.end());
    
    return steps;
}

// 打印过桥步骤
void printSteps(const vector<Step>& steps, long long min_time) {
    cout << "最短时间: " << min_time << " 分钟" << endl;
    cout << "追兵到达时间: 24 分钟" << endl;
    if (min_time <= 24) {
        cout << "能逃脱!\n" << endl;
    } else {
        cout << "不能逃脱。\n" << endl;
    }
    
    cout << "具体步骤:\n";
    int total_time = 0;
    for (int i = 0; i < steps.size(); i++) {
        total_time += steps[i].time;
        cout << "步骤" << i+1 << ": ";
        
        if (steps[i].person2.empty()) {
            cout << steps[i].person1 << "独自" 
                 << (steps[i].direction == "→" ? "过桥到右岸" : "返回左岸");
        } else {
            cout << steps[i].person1 << "和" << steps[i].person2 
                 << "一起过桥" << (steps[i].direction == "→" ? "到右岸" : "到左岸");
        }
        
        cout << " 【耗时 " << steps[i].time << " 分钟,累计 " << total_time << " 分钟】" << endl;
    }
    
    cout << "\n总时间: " << total_time << " 分钟" << endl;
}

int main() {
    // 输入数据
    vector<Person> people = {
        Person("爸爸", 2),
        Person("妈妈", 4),
        Person("乔安娜", 8),
        Person("奶奶", 10)
    };
    
    // 按时间排序
    sort(people.begin(), people.end());
    
    // 求解最小过桥时间
    auto [min_time, choice] = solveBridgeCrossing(people);
    
    // 构建过桥步骤
    vector<Step> steps = buildSteps(people, choice);
    
    // 打印结果
    printSteps(steps, min_time);
    
    return 0;
}