P1434 滑雪(记忆化搜索 / 线性DP)

题目描述:

题目传送门


解题思路:

此题可以考虑使用记忆化搜索和动态规划来做。

记忆化搜索:

爆搜做法很容易可以想出来,但是时间会T,因此我们要加上记忆化优化搜索。

记忆化即为记下相同状态的最优解,当下一次搜索到的时候避免重复调用,以此来优化时间。
接下来就是深搜一波了。

#include <iostream>
using namespace std;
long long R,C,a[101][101],num[101][101]={0},M=0;
bool check(int nx,int ny)
{
	return 1<=nx && nx<=R && 1<=ny && ny<=C;
}
int Maxnum(int sx,int sy)
{
	if(num[sx][sy]>0)  //使用 num 数组来记下最优解,避免重复计算
	  return num[sx][sy];
	int ans=0;
	const int dx[5]={0,0,0,1,-1},dy[5]={0,1,-1,0,0};  
	for(int i=1;i<=4;i++)  //尝试模拟滑雪的路径递归
	  {
	  	int nx=sx-dx[i],ny=sy-dy[i];
	  	if(!check(nx,ny)) continue;
	  	if(a[nx][ny]>=a[sx][sy]) continue;
	  	int t=Maxnum(nx,ny);
	  	if(ans<t)
	  	  ans=t;
	  }
	num[sx][sy]=ans+1;
	return ans+1;  //返回当前状态最优解
}
int main()
{
	cin>>R>>C;
	for(int i=1;i<=R;i++)
	  for(int j=1;j<=C;j++) cin>>a[i][j];
	for(int i=1;i<=R;i++)  //尝试每一个点都走一次,存下最优解
	  for(int j=1;j<=C;j++)
	    {
	      Maxnum(i,j);
	      if(M<num[i][j]) M=num[i][j];
	    }
	cout<<M;
	return 0; 
} 
动态规划线性DP做法:

可以发现,如果是按正常来说,由于在地图中可以走上下左右四个方向,因此它不具备最有子结构性。但是我们可以考虑将二维的地图压缩成一维,将问题转换为线性DP。

压缩地图:

首先我们要地图中每一个点标上一个序号(注意只是序号,不是题目中的高度)

在这里插入图片描述
因此,我们可以根据这个序号把地图按顺序展开成一维的,并在原本地图对应序号的地方存下高度,如(高度就用题目数据来填吧):

序号:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

高度: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

此时我们再按照高度将序号从小到大排序(注意高度对应的编号永远不变)。
这时候我们可以发现,高的地方可以滑到低的地方,也就是说,如果这张一维的地图有序,在这个序列中,高的地方的最优解可以从低的地方转移,也就是说若 i i i 点的的高度大于 j j j 点,那么 i i i 点的最优解可以考虑从 j j j 点的最优解转移。
并且 j j j 的答案由于在先后上先于 i i i 被枚举到,因此已经 j j j 点在枚举到 i i i 点时已经是最优的了,此时,最优子结构性就满足了。

设二维地图的规模为 n ∗ m n*m nm,根据我们二维地图编号与一维地图编号的性质,可以发现:若 x x x 为当前点的编号,那么 x x x 上方的点的编号为 x − m x-m xm,下方点的编号为 x + m x+m x+m,左侧点的编号为 x − 1 x-1 x1 ,右侧点的编号为 x + 1 x+1 x+1
然后再处理一下边界条件,若 x − m ≤ 0 x-m\leq0 xm0 则说明 x x x 位于第 1 1 1 行;若 x + m > n ∗ m x+m>n*m x+m>nm ,那么 x x x 在第 n n n 行;若 x % m = 0 x\%m=0 x%m=0, 则 x x x 在第 m m m 列,若 ( x − 1 ) % m = 0 (x-1)\% m=0 (x1)%m=0, 则 x x x 在第 1 1 1 列。

f i f_i fi 表示编号为 i i i 的点的最远滑行距离,那么很显然:
f i = m a x { f i f i + m + 1 i + m ≤ n ∗ m f i − m + 1 i − m > 0 f i + 1 + 1 i % m ≠ 0 f i − 1 + 1 ( i − 1 ) % m ≠ 0 f_i=max\begin{cases} f_i\\ f_{i+m}+1&i+m\leq n*m\\ f_{i-m}+1&i-m>0\\ f_{i+1}+1&i\%m\ne 0\\ f_{i-1}+1&(i-1)\%m\ne0 \end{cases} fi=maxfifi+m+1fim+1fi+1+1fi1+1i+mnmim>0i%m=0(i1)%m=0

由于无论如何滑行,路程都包括是这个点本身,因此初值设 f i = 1 f_i=1 fi=1

注意我们不能直接从 i = 1 ⋯ n ∗ m i=1\cdots n*m i=1nm 来枚举,由于编号是进过高度排序的,因此我们要按照这个排序后的编号来枚举。设 s i s_i si 表示第 i i i 个编号,也就是说 i = s 1 ⋯ s n ∗ m i=s_1\cdots s_{n*m} i=s1snm

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,m;
int a[360010],s[360010];
bool cmp(int x,int b)
{
	return a[x]<a[b];
}
int f[360010]={0},ans=0;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n*m;i++) s[i]=i;
	for(int i=1;i<=n;i++)
	  {
	  	for(int j=1;j<=m;j++)
	  	  {
	  	  	cin>>a[(i-1)*m+j]; 
		  }
	  }
	sort(s+1,s+1+n*m,cmp);  
/*由于要保证只有序号进行排序但是高度不变,所以不能用结构体,
因为结构体会序号排了高度也跟着排,这会出现 第一个序号对应最高高度,第二个序号对应次高高度……的情况,
地图就被改变了。*/
	for(int i=1;i<=n*m;i++)
	  {
	  	f[s[i]]=1;
	  	if(s[i]-m>0&&a[s[i]]>a[s[i]-m]) 
	  	
		  f[s[i]]=max(f[s[i]],f[s[i]-m]+1);
		  
		  
	  	if(s[i]+m<=n*m&&a[s[i]]>a[s[i]+m]) 
	  	
		  f[s[i]]=max(f[s[i]],f[s[i]+m]+1);
		  
		  
	  	if((s[i]-1)%m!=0&&a[s[i]]>a[s[i]-1]) 
	  	
		  f[s[i]]=max(f[s[i]],f[s[i]-1]+1);
		  
		  
	  	if(s[i]%m!=0&&a[s[i]]>a[s[i]+1]) 
	  	
		  f[s[i]]=max(f[s[i]],f[s[i]+1]+1);
		  
		  
	  	ans=max(ans,f[s[i]]);
	  }
	cout<<ans;
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值