P1135 奇怪的电梯(深度优先搜索优化)
题目描述
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼(1≤i≤N)上有一个数字 K**i(0≤K**i≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3,3,1,2,5 代表了 K**i(K1=3,K2=3,……),从 1 楼开始。在 1 楼,按“上”可以到 4 楼,按“下”是不起作用的,因为没有 −2 楼。那么,从 A 楼到 B 楼至少要按几次按钮呢?
输入格式
共二行。
第一行为三个用空格隔开的正整数,表示 N,A,B(1≤N≤200,1≤A,B≤N)。
第二行为 N 个用空格隔开的非负整数,表示 K**i。
输出格式
一行,即最少按键次数,若无法到达,则输出 -1
。
输入输出样例
输入
5 1 5
3 3 1 2 5
输出
3
说明/提示
对于 100% 的数据,1≤N≤200,1≤A,B≤N,0≤K**i≤N。
本题共 16 个测试点,前 15 个每个测试点 6 分,最后一个测试点 10 分。
代码如下:
#include <bits/stdc++.h>
using namespace std;
vector<int> myOne; // 存储每层楼的 K_i 值
int nn; // 总楼层数 N
int min_count = 1e9; // 记录最少按键次数,初始设为极大值
int A, B; // 起始楼层 A 和目标楼层 B
bool flag = false; // 标记是否找到可行解
vector<bool> v; // 标记楼层是否被访问过,防止循环
vector<int> best; // best[i] 表示到达 i 层时的已知最小按键次数,用于剪枝优化
// 深度优先搜索函数,x 是当前楼层,count 是已按的次数
void dfs(int x, int count) {
// 剪枝1:当前按键次数已超过已知的最小值,无需继续搜索
if (count >= min_count)
return;
// 剪枝2:当前到达 x 层的次数不比之前记录的更优,无需继续
if (count >= best[x])
return;
best[x] = count; // 更新到达 x 层的最优解
// 边界检查:楼层超出有效范围
if (x < 1 || x > nn)
return;
// 到达目标楼层,更新最小次数和标记
if (x == B) {
flag = true;
min_count = min(count, min_count);
return;
}
// 尝试向上移动:计算新楼层 next_up
int next_up = x + myOne[x];
if (next_up <= nn && !v[next_up]) { // 检查是否越界且未被访问
v[next_up] = true; // 标记为已访问
dfs(next_up, count + 1); // 递归搜索
v[next_up] = false; // 回溯,取消标记
}
// 尝试向下移动:计算新楼层 next_down
int next_down = x - myOne[x];
if (next_down >= 1 && !v[next_down]) { // 检查是否越界且未被访问
v[next_down] = true;
dfs(next_down, count + 1);
v[next_down] = false;
}
}
int main() {
cin >> nn >> A >> B;
myOne.resize(nn + 1); // 调整大小,下标从 1 开始
v.resize(nn + 1, false); // 初始化访问标记数组
best.resize(nn + 1, 1e9); // 初始化最优数组,默认值为极大值
// 读取每层的 K_i 值
for (int i = 1; i <= nn; i++) {
cin >> myOne[i];
}
v[A] = true; // 标记起始楼层已访问
dfs(A, 0); // 从起始楼层开始搜索,初始按键次数为 0
if (!flag) { // 未找到可行路径
cout << -1;
} else { // 输出最少按键次数
cout << min_count;
}
return 0;
}
解决思路
- 问题建模
将电梯移动过程视为状态转换问题。每个状态由当前楼层和已按次数组成,目标是找到从 A 到 B 的最短路径(最少按键次数)。 - 深度优先搜索(DFS)
从起始楼层 A 出发,递归尝试所有可能的向上/向下移动。每次移动后更新按键次数,并记录到达新楼层的最优解。 - 剪枝优化
- 最优性剪枝:若当前路径的按键次数已超过已知的最小值,或到达某楼层时次数不比之前记录的更优,则提前终止该路径搜索。
- 循环检测:通过访问标记数组
v
避免在同一路径中重复访问同一楼层,防止无限循环。
- 记忆化优化
best
数组记录到达每层的最小按键次数。当后续搜索到达同一楼层但次数更多时,直接剪枝,避免重复计算。 - 边界处理
检查楼层是否在有效范围内(1~N),确保移动的合法性。