赞
踩
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 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, … \dots … … \dots …),从 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
。
5 1 5
3 3 1 2 5
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。
引入一个前置知识,记忆化搜索。
记忆化搜索就是在搜索时记录一些有用的答案, 我们递归的本质就是在搜索答案,但是有些问题会被重复的搜索,所以我们就可以用空间换时间的思想, 将被搜索的问题的答案记录下来, 当下一次再被搜索到这个问题的时候, 就可以在 O ( 1 ) O(1) O(1) 的时间给出答案。
int ans[1005];
int saerch(int n) {
if (n == 1 || n == 2) return 1;
if (ans[n] == 0) ans[n] = saerch(n - 1) + saerch(n - 2);
return ans[n];
}
记忆化搜索实际上就是在搜索过程中不做重复的计算,遇到相同的搜索分支直接调用上次的计算结果,是实现动态规划强有力的方法之一,另外一种实现动态规划的方法就是递推,这里不做过多说明。
题目要解决的问题为:从
A
A
A 楼是否可以到达
B
B
B 楼?如果可以,最少要按几次按钮?
很容易想到
D
F
S
DFS
DFS 爆搜结果,但
1
≤
N
≤
200
1 \le N \le 200
1≤N≤200 显然是会超时的(亲测
20
20
20 分),因为险恶的出题人很容易就能把你的深度优先搜索卡在死循环里出不来,比如下面这个样例:
5 1 5
2 3 2 3 5
对于这个样例,搜索过程中必定有一个路径会从 1 1 1 层升到 3 3 3 层,然后从 3 3 3 层升到 5 5 5 层,接着 5 5 5 层再降到 1 1 1 层 … \dots … … \dots … 如此循环往复,必然会超时,所以需要考虑优化剪枝。 20 20 20 分代码如下:
#include<bits/stdc++.h> using namespace std; int n,b,minn=INT_MAX,k[205]; void dfs(int l,int cnt){ //l记录当前所在层,cnt记录步数 if(l==b){ //如果到达b层,更新最小值 minn=min(minn,cnt); return ; } if(l<1||l>n) return ; //如果超出电梯能到达的范围,果断推出 dfs(l-k[l],cnt+1),dfs(l+k[l],cnt+1); //往上或是往下走,计数器加一 } int main() { int a; scanf("%d%d%d",&n,&a,&b); for(int i=1;i<=n;i++) scanf("%d",&k[i]); dfs(a,0); //从第a层开始,此时计数器为0 if(minn==INT_MAX) printf("-1"); //如果搜索结束之后记录答案的minn仍为初始值, //说明minn没有更新过,则没有方案可以到达b层 else printf("%d",minn); return 0; }
分析刚才给出的样例能卡掉搜索的原因,无非是在搜索过程中会经常遇到之前走过的路径或是走到当前这层所用的步数大于等于先前来到这层经过的步数,从而导致超时。对于之前走过的路径我们已经知道答案,而对于步数比以前多的情况一定不是最优解,所以上述两种情况完全没有必要继续搜索,这就完成了剪枝。搜索部分代码实现如下:
int n,b,minn=INT_MAX,k[201],f[201];
//f[i]记录到这层经过的步数的最小值,同时避免了重复经过同意路径的情况
void dfs(int l,int cnt){
if(l==b){
minn=min(minn,cnt);
return ;
}
if(l<1||l>n||cnt>=f[l]||cnt>=minn) return ;
//如果走到当前这层所用的步数大于等于先前来到这层经过的步数,
//或是计数器已经大于等于到达b层步数的最小值,果断退出
f[l]=cnt;
//否则更新到这层经过的步数的最小值
dfs(l-k[l],cnt+1),dfs(l+k[l],cnt+1);
}
#include<cstring> #include<iostream> using namespace std; int n,b,minn=INT_MAX,k[205],f[205]; void dfs(int l,int cnt){ if(l==b){ minn=min(minn,cnt); return ; } if(l<1||l>n||cnt>=f[l]||cnt>=minn) return ; f[l]=cnt; dfs(l-k[l],cnt+1),dfs(l+k[l],cnt+1); } int main() { int a; scanf("%d%d%d",&n,&a,&b); for(int i=1;i<=n;i++) scanf("%d",&k[i]); memset(f,5,sizeof f); dfs(a,0); if(minn==INT_MAX) printf("-1"); else printf("%d",minn); return 0; }
-------------------------------------------------------请-------------------勿-------------------抄-------------------袭-------------------------------------------------------
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。