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
n∗m,根据我们二维地图编号与一维地图编号的性质,可以发现:若
x
x
x 为当前点的编号,那么
x
x
x 上方的点的编号为
x
−
m
x-m
x−m,下方点的编号为
x
+
m
x+m
x+m,左侧点的编号为
x
−
1
x-1
x−1 ,右侧点的编号为
x
+
1
x+1
x+1。
然后再处理一下边界条件,若
x
−
m
≤
0
x-m\leq0
x−m≤0 则说明
x
x
x 位于第
1
1
1 行;若
x
+
m
>
n
∗
m
x+m>n*m
x+m>n∗m ,那么
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
(x−1)%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=max⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧fifi+m+1fi−m+1fi+1+1fi−1+1i+m≤n∗mi−m>0i%m=0(i−1)%m=0
由于无论如何滑行,路程都包括是这个点本身,因此初值设 f i = 1 f_i=1 fi=1。
注意我们不能直接从 i = 1 ⋯ n ∗ m i=1\cdots n*m i=1⋯n∗m 来枚举,由于编号是进过高度排序的,因此我们要按照这个排序后的编号来枚举。设 s i s_i si 表示第 i i i 个编号,也就是说 i = s 1 ⋯ s n ∗ m i=s_1\cdots s_{n*m} i=s1⋯sn∗m
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!