题目描述
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i i i 层楼( 1 ≤ i ≤ N 1 \le i \le N 1≤i≤N)上有一个数字 K i K_i Ki( 0 ≤ K i ≤ N 0 \le K_i \le N 0≤Ki≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3 , 3 , 1 , 2 , 5 3, 3, 1, 2, 5 3,3,1,2,5 代表了 K i K_i Ki( K 1 = 3 K_1=3 K1=3, K 2 = 3 K_2=3 K2=3,……),从 1 1 1 楼开始。在 1 1 1 楼,按“上”可以到 4 4 4 楼,按“下”是不起作用的,因为没有 − 2 -2 −2 楼。那么,从 A A A 楼到 B B B 楼至少要按几次按钮呢?
输入格式
共二行。
第一行为三个用空格隔开的正整数,表示 N , A , B N, A, B N,A,B( 1 ≤ N ≤ 200 1 \le N \le 200 1≤N≤200, 1 ≤ A , B ≤ N 1 \le A, B \le N 1≤A,B≤N)。
第二行为 N N N 个用空格隔开的非负整数,表示 K i K_i Ki。
输出格式
一行,即最少按键次数,若无法到达,则输出 -1
。
输入输出样例 #1
输入 #1
5 1 5
3 3 1 2 5
输出 #1
3
说明/提示
对于 100 % 100 \% 100% 的数据, 1 ≤ N ≤ 200 1 \le N \le 200 1≤N≤200, 1 ≤ A , B ≤ N 1 \le A, B \le N 1≤A,B≤N, 0 ≤ K i ≤ N 0 \le K_i \le N 0≤Ki≤N。
本题共 16 16 16 个测试点,前 15 15 15 个每个测试点 6 6 6 分,最后一个测试点 10 10 10 分。
思路解析
由题可知,求最少按键次数(类似于求最短路径),并且每次只能移动 K i K_i Ki层(向上或向下),而且移动后楼层必须在 [1, n] 范围内。
第一种思路:由于在无权图最短路径问题中优先使用BFS(因为BFS按层扩展(在这里即表现为从起点开始,按按键次数递增顺序探索所有可能的楼层),首次到达目标时的路径即为最短路径(即目标楼层首次被访问时的次数即为最少按键次数))代码如下所示:
#include<stdio.h>
int n;
int num[201];
int visited[201];//用于标记是否被访问过
typedef struct{
int floor;//表示楼层
int step;//表示到达当前楼层所需要的步数
}node;
int bfs(int start,int end);
int main()
{
int a,b,i,j,result;
scanf("%d %d %d",&n,&a,&b);
for(i=1;i<=n;i++)
{
scanf("%d",&num[i]);
}
result=bfs(a,b);
printf("%d\n",result);
return 0;
}
int bfs(int start,int end)
{
int front,rear;
node queue[201];
if(start==end)return 0;
//初始化
front=rear=0;
//入队
queue[rear].floor=start;
queue[rear].step=0;
rear++;
//标记已被访问
visited[start]=1;
while(front<rear)//当队列不空时进行循环
{
node current;
//取队头元素,并出队
current=queue[front++];
//向上移动
if(current.floor+num[current.floor]<=n){
if(current.floor+num[current.floor]==end)//终止条件
{
return current.step+1;
}
if(!visited[current.floor+num[current.floor]])//标记未被访问
{ //入队
queue[rear].floor=current.floor+num[current.floor];
queue[rear].step=current.step+1;
rear++;
//标记已被访问
visited[current.floor+num[current.floor]]=1;
}
}
//向下移动
if(current.floor-num[current.floor]>=1){
if(current.floor-num[current.floor]==end)//终止条件
{
return current.step+1;
}
if(!visited[current.floor-num[current.floor]])//标记未被访问
{ //入队
queue[rear].floor=current.floor-num[current.floor];
queue[rear].step=current.step+1;
rear++;
//标记已被访问
visited[current.floor-num[current.floor]]=1;
}
}
}
return -1;//队列为空时表示不可到达
}
第二种思路:采用DFS+剪枝,由于DFS会遍历所有的路径,就可能存在绕远路以及存在环路时可能陷入无限递归的现象,所以需要记录到达某楼层的最小步数,为防止运行超时和内存超限,需要进行剪枝。代码如下所示:
#include<stdio.h>
int n;
int step=2147483647;
int num[201];
int visited[201];
/* 记录到达该楼层的最小步数,
既可以避免陷入递归的死循环,
又可以更加正确的找到最短路径.
当输入 4 1 4
2 1 2 1时会存在循环路径但无法到达终点,
若不标记每个楼层是否被访问过 ,就会一直在1层和3层之间来回循环,
因此需要一个标记数组,但是,可能存在一开始访问的某层的路径不是最短的,
后来再次访问这一层的路径是最短的,若只采用简单的标记数组,无法找到最短路径 ,
所以用 visited数组记录到达该楼层的最小步数。
*/
void dfs(int current,int target,int count);
int main()
{
int i,j,a,b;
scanf("%d %d %d",&n,&a,&b);
for(i=1;i<=n;i++)
{
scanf("%d",&num[i]);
visited[i]=2147483647;
}
dfs(a,b,0);
if(step!=2147483647)printf("%d\n",step);
else printf("-1\n");//step不变表示无法到达
return 0;
}
void dfs(int current,int target,int count)
{
if(count>=step) return ; // 剪枝条件1:当前步数已超过已知最短路径
if(visited[current]<=count) return ;// 剪枝条件2:当前路径不是更优解
visited[current]=count;//更新到当前楼层的最小步数
if(current==target)//终止条件
{
if(step>count)
{
step=count;
}
return ;
}
if(current-num[current]>=1)//向下移动
{
dfs(current-num[current],target,count+1);
}
if(current+num[current]<=n){//向上移动
dfs(current+num[current],target,count+1);
}
}