P1135 奇怪的电梯

题目描述

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i i i 层楼( 1 ≤ i ≤ N 1 \le i \le N 1iN)上有一个数字 K i K_i Ki 0 ≤ K i ≤ N 0 \le K_i \le N 0KiN)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 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 1N200 1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN)。

第二行为 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 1N200 1 ≤ A , B ≤ N 1 \le A, B \le N 1A,BN 0 ≤ K i ≤ N 0 \le K_i \le N 0KiN

本题共 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);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值