题解 P6507 【[COCI2007-2008] NIKOLA】
linyinuo2008 · · 题解
关键字:记忆化搜索
这是道记搜好题,看到之后决定来一发。
看到这题格子长度能达到虽然我还是傻傻的打了一份暴搜的代码
之所以看到这题就想到搜索,因为有如下几个特征。
-
是跟格子有关的题,所以
简单算法可能种类就是动态规划,贪心,搜索,和入门无算法题。 -
有后效性,排除贪心。
-
数据范围很适合搜索,可能需要一些优化。
-
不过想到动态规划很正常,下面我们来看一下为什么动态规划做不了这题。
动态规划的想法
我们来仔细想一下这题用动归该怎样配置。
首先,我们可以先设
于是我们设
别忘了判断越界,其中
答案:
于是问题来了,我们应当如何枚举这不就是搜索吗)。
暴搜基本思路
-
判断是否到达终点,若到达,取当前答案与此次结果最小值,返回。
-
判断是否越界,若越界,则返回。
-
向
1 号格子方向搜索。 -
向终点方向搜索。
递归函数参数:pos(当前格子),len(走到这个格子走过的距离),money(已花钱数)。
结果:TLE6个点。
What do you expect?
记忆化搜索优化开始。
记忆化搜索思路
用一个
-
判断是否越界,若越界返回一个巨大值,卡死这条路。
-
如果到了最后一个格子,就返回这个格子的钱数。
-
如果当前点已被记录过,就直接返回就可以了(这就是记搜的优化精髓)。
-
向两边继续搜记录在
vis 里,取最小值,别忘加当前格子钱数欧。 -
返回初始点
vis 值。
递归函数参数:pos(当前格子),len(走到这个格子走过的距离)。
这里说一下为什么只用两个参数,而不用加一个代表上一步方向的变量dir。因为我们考虑的是在这一个格子往前跳到终点所花钱数,而与它上一步从哪个格子来的无关。我们考虑的只是钱数,并不关心他上一个格子的花费。
我们需要记录上一步步数是因为这与下一步走的步数有关(可能加一)。
下面上代码(有注释):
#include <iostream>
using namespace std;
const int N=1001;
const int INF=0x7f7f7f;//设的大一些
int ans,n,a[N];
int min(int a,int b)
{
return a<b?a:b;
}
void dfs(int pos,int len,int money)
//money是当前所需的钱数,pos是当前的位置,len是上次走的步数
{
if(pos==n) ans=min(ans,money+a[pos]);//到达更新ans
if(pos<1||pos>n) return ;//判越界
//两边搜
dfs(pos-len,len,money+a[pos]);
dfs(pos+len+1,len+1,money+a[pos]);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
ans=INF;//别忘记初始化ans为无穷大
dfs(2,1,0);//从第二个格子开始搜
cout<<ans;
return 0;
}
#include <iostream>
using namespace std;
const int N=1001;
//vis[pos][len]表示现在在第pos个格子,上次走了len步时所花的最少钱数
int n,a[N],vis[N][N];
int min(int a,int b)//手动写min
{
return a<b?a:b;
}
int search(int pos,int len)
//pos是当前的位置,len是上次走的步数
{
if(pos<1||pos>n) return 0x7f7f7f7f;//这个点好毒瘤,写0x7f都过不了
if(pos==n) return a[n];//到终点返回钱数
if(vis[pos][len]) return vis[pos][len];//记忆化返回
return vis[pos][len]=min(search(pos-len,len),search(pos+len+1,len+1))+a[pos];//向两边搜
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
cout<<search(2,1);//从第二个格子开始搜
return 0;
}
注:这题数据真不错,记搜写成0x7f一个点都过不了。
管理员大大求通过!