P1434 [SHOI2002] 滑雪
前言
这道题目虽然在“动态规划的引入”里,但是如何找到可以表示某一“缓存状态”是比较困难的,我认为这也是动态规划题目的难点之一。我认为第一是要判断本题可以使用动态规划,第二是要判断怎么找到缓存状态(怎么找到相邻步数之间的方程式F(i)=G(i-1)
)。
题目
题目描述
Michael 喜欢滑雪。这并不奇怪,因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael 想知道在一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度会减小。在上面的例子中,一条可行的滑坡为 24 − 17 − 16 − 1 24-17-16-1 24−17−16−1(从 24 24 24 开始,在 1 1 1 结束)。当然 25 25 25- 24 24 24- 23 23 23- … \ldots …- 3 3 3- 2 2 2- 1 1 1 更长。事实上,这是最长的一条。
输入格式
输入的第一行为表示区域的二维数组的行数 R R R 和列数 C C C。下面是 R R R 行,每行有 C C C 个数,代表高度(两个数字之间用 1 1 1 个空格间隔)。
输出格式
输出区域中最长滑坡的长度。
样例 #1
样例输入 #1
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
样例输出 #1
25
提示
对于 100 % 100\% 100% 的数据, 1 ≤ R , C ≤ 100 1\leq R,C\leq 100 1≤R,C≤100。
题目分析
动态规划分析
看到本题我的第一想法是搜索算法,每种情况都遍历搜索一遍,然后找到最长的。但是这种想法显然是最笨的,况且我们是题组,是知道这题用动态规划的。那就朝动态规划这个方向想。这题还很坏,就是测试用例基本没有什么想法上的参考价值。现在的关键就是要找到某个缓存状态。
我们想到最长路径是怎么加一的,是不是看到旁边有比你小的就可以加一?我们假设a[i][j](比如例子里的17)是最长最长路径上的一个点。那么a[i][j]要找到下一步就是要找到比自己小的周围的点。(比如17找到的是16和2显然16更优,因为16后选择更多,2后基本没啥选择了)。f[i[j]=max{f[i+a][j+b]}+1
还需要注意不能到重复的点,然后从动态规划的角度考虑,f[i+a][j+b]需要在f[i][j]之前就要计算出来,所以还需要排序一下,从低的开始算。但是排序又不能改变数组的位置,所以要么做一个结构体,要么就是间接排序(使用另一个数组来记录他们的位置)。OK这就是基本思想啦。现在开始实现!
遇到了几个问题。前面提到的两种排序都有具体实现的难题。间接排序好在比较好判断相邻,但是问题是无法使用二维数组代表排序结果,所以需要一个一维数组,数组的顺序代表大小,数组的值代表行列信息index=
x*C+y`(或者一维结构体数组包括行列信息)。结构体的好处是比较便于排序,我也是使用结构体的,但是缺点在于需要另外判断相邻,需要多写一个函数。
另一种解法
说实话,这题我也是半学习半摸索才做出动态规划的,因为直接秒杀我也不需要再加练题目了。但是本题动态规划比较难以理解,所以我尝试使用另一种方法——记忆化搜索来解决一下。也是扩宽一下思维。
注意事项
1.怎么找状态转移方程,从要求的值是怎么变化的来找
2.排序和相邻便利的选择
3.同样还是maxx不能和max函数同名,sort函数需要#include<algorithm>
或者万能头#include<bits/stdc++.h>
代码
历时两天一夜(的空闲时间)终于做出来了
动态规划做法
动态规划代码如下(790ms/2.09MB)
int main(){
int R,C,n,maxx=0;
cin>>R>>C;
n=R*C;
for(int i=0;i<n;i++){
cin>>a[i].h;
a[i].row=i/C;
a[i].column=i%C;
}
sort(a,a+n,cmp);//排序
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
if(check(a[i],a[j])&&a[i].h>a[j].h){
a[i].len=max(a[i].len,a[j].len+1);//动态规划方程
}
}
}
for(int i=0;i<n;i++){
if(a[i].len>maxx)
maxx=a[i].len;
}
cout<<maxx<<endl;
return 0;
}
记忆化搜索
这个做法更适合本题,速度更快
记忆化搜索代码如下(34ms/816.00KB)
//记忆化搜索
#include<iostream>
#include<algorithm>
using namespace std;
int dx[4]={0,1,-1,0};
int dy[4]={1,0,0,-1};
int a[107][107],s[107][107],R,C;
int dfs(int x,int y){//记忆化搜索
if(s[x][y])return s[x][y];//已经有了不用算
s[x][y]=1;
for(int i=0;i<4;i++)
{ int nowx=dx[i]+x;
int nowy=dy[i]+y;
if(nowx>=0&&nowy>=0&&nowx<R&&nowy<C&&a[x][y]>a[nowx][nowy]){
dfs(nowx,nowy);//递归
s[x][y]=max(s[x][y],s[nowx][nowy]+1);//跟动态规划相似
}
}
return s[x][y];
}
int main()
{
int maxx=0;
cin>>R>>C;
for(int i=0;i<R;i++)
for(int j=0;j<C;j++)
cin>>a[i][j];
for(int i=0;i<R;i++)//找从每个出发的最长距离
for(int j=0;j<C;j++)
maxx=max(maxx,dfs(i,j));
cout<<maxx<<endl;
return 0;
}
后话
记忆化搜索和动态规划区别
记忆化搜索本质思想有点类似于动态规划,但是不同于,动态规划可以对数组进行降维,在保持时间复杂度不变的情况下,能有效降低空间复杂度。并且,由于动态规划方法不涉及递归,不需要回溯。其缺点是需要计算所有状态的价值函数。
记忆化搜索的优点在于不需要计算所有状态的价值函数,相当于对整个递归过程进行剪枝,其最差复杂度和动态规划方法是一个量级。其缺点是虽然可能不需要计算所有状态价值函数,但很多问题通常都需要计算所有状态价值函数的值,而记忆化搜索还有一个回溯的过程,在需要计算大多数状态价值函数值时记忆化搜索不一定能比动态规划要快,甚至可能比动态规划要慢。并且,尽管我们不需要计算所有状态的价值函数,但是为了检索方便通常以哈希表储存,要分配所有价值函数的空间,空间复杂度一般比动态规划方法要大。
王婆卖瓜
感觉有收获或者想跟上我的进度刷题的,可以点个关注,或者点赞收藏评论都可以!
参考来源
SHOI2002上海省选题
洛谷链接
动态规划和记忆化搜索