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人一起过桥
· 只有一盏灯(需要有人带回,本题为通行证)
· 每个人过桥时间不同
· 求所有人过桥的最短时间
算法思路
- 排序 先将所有人按过桥时间从小到大排序:
· times[0] 是最快的人
· times[1] 是次快的人
· times[n-1] 是最慢的人
- 基础情况 · n=0:0分钟
· n=1:只需自己过桥,times[0]分钟
· n=2:两人一起过,取较慢者的时间 times[1]
· n=3:最快的人来回送灯,时间为 times[0]+times[1]+times[2]
- 动态规划核心(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]
- 时间复杂度
· 排序: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;
}