题解:P1508 Likecloud-吃、吃、吃
题意简述
给定一个 \(m \times n\) 的矩阵,矩阵中每个点都有权值 \(a_{i,j}\),从最底下一排的中点下面的地方开始往上走,每次可以走到正上方,左上方或右上方。最大化走到最顶上一排的路径上的点权值之和。
那换言之,就是第一步能走到最底下一排的中间三个,然后依次向外拓展。
思路
我们从正向与逆向两个方向分别考虑,不难萌生出两种写法:
-
这个题显然是可以用搜索做的。从最底下的点开始,每次向上搜,可以搜正上,左上,右上三个方向。于是很好写出暴力代码,但是会超时。
#include<bits/stdc++.h> #define ll long long #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) using namespace std; const int N=205; const int inf=0x3f3f3f3f; int m,n,a[N][N],ans; int dfs(int i,int j,int res){ if(i==1)return res; int w=-inf; for(int k=j-1;k<=j+1;k++){ if(1<=k&&k<=n)w=max(w,dfs(i-1,k,res)+a[i-1][k]); } return res+w; } int main(){ ios;cin>>m>>n; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ cin>>a[i][j]; } } cout<<dfs(m+1,(n+1)/2,0)<<'\n'; return 0; }这意味着暴搜不行,考虑优化。不难看出,暴搜的局限性在于很多点重复搜索了很多次,因此考虑记忆化搜索。
这个名字听起来很高端,实际上思路很简单:将一个点搜索后的结果存储在一个数组,例如 \(f\) 中,如果后面在搜索到这个点了,就不再重新搜一遍,而是直接返回 \(f\) 数组中对应的这个点的搜索结果。于是每个点最多只会被搜索到一遍,时间复杂度就降下来了。
#include<bits/stdc++.h> #define ll long long #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) using namespace std; const int N=205; const int inf=0x3f3f3f3f; int m,n,a[N][N],f[N][N]; bool vis[N][N]; int dfs(int i,int j){ if(i<1)return 0; if(vis[i][j])return f[i][j]; vis[i][j]=1; f[i][j]=-inf; for(int k=j-1;k<=j+1;k++){ if(1<=k&&k<=n){ f[i][j]=max(f[i][j],dfs(i-1,k)+a[i-1][k]); } } return f[i][j]; } int main(){ ios;cin>>m>>n; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ cin>>a[i][j]; } } cout<<dfs(m+1,(n+1)/2)<<'\n'; return 0; } -
我们考虑从上往下走,显然只要最后到达了最底下一排中间的三个点就是一条合法路径,可以倒着走回去就能符合题意。这样也是往正下、左下、右下三个方向走,因此每一个点只能来自于它正上、左上、右上三个方向。
因此可以选取这三个点中最大的一个作为自己的上一步,再加在自己的权值上。最后对最底下一排的中间三个点的权值求最大值即可。
这种动态规划的想法很好想代码也比较好实现。这里写一下转移方程式。
用 \(f_{i,j}\) 表示一个点更新后的权值,则有:
\[f_{i,j} = \max(f_{i-1,j-1},f_{i-1,j},f_{i-1,j+1})+a_{i.j} \]边界条件:
\[f_{1,j}=a_{1,j},f_{i,0}=f_{i,n+1}=- \infty \]#include<bits/stdc++.h> #define ll long long #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) using namespace std; const int N=205; const int mod=1e9+7; const int inf=0x3f3f3f3f; int m,n; int a[N][N],f[N][N]; int main(){ ios;cin>>m>>n; for(int i=1;i<=m;i++){ f[i][0]=f[i][n+1]=-inf; for(int j=1;j<=n;j++)cin>>a[i][j]; } for(int j=1;j<=n;j++)f[1][j]=a[1][j]; for(int i=2;i<=m;i++){ for(int j=1;j<=n;j++){ int x=f[i-1][j-1]; int y=f[i-1][j]; int z=f[i-1][j+1]; f[i][j]=max({x,y,z})+a[i][j]; } } int ans=-inf,mid=(n+1)/2; for(int j=mid-1;j<=mid+1;j++)ans=max(ans,f[m][j]); cout<<ans<<'\n'; return 0; }
尾声
这篇题解主要是将两种方法进行一个整理,主要讲述思路,希望对你有帮助。
完结撒花!
本文来自博客园,作者:Circle_Table,转载请注明原文链接:https://www.cnblogs.com/Circle-Table/articles/19503036

浙公网安备 33010602011771号