题解:P1508 Likecloud-吃、吃、吃

这边是题目传送门喵!

题意简述

给定一个 \(m \times n\) 的矩阵,矩阵中每个点都有权值 \(a_{i,j}\),从最底下一排的中点下面的地方开始往上走,每次可以走到正上方,左上方或右上方。最大化走到最顶上一排的路径上的点权值之和。

那换言之,就是第一步能走到最底下一排的中间三个,然后依次向外拓展。

思路

我们从正向与逆向两个方向分别考虑,不难萌生出两种写法:

  1. 这个题显然是可以用搜索做的。从最底下的点开始,每次向上搜,可以搜正上,左上,右上三个方向。于是很好写出暴力代码,但是会超时。

    #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;
    }
    
  2. 我们考虑从上往下走,显然只要最后到达了最底下一排中间的三个点就是一条合法路径,可以倒着走回去就能符合题意。这样也是往正下、左下、右下三个方向走,因此每一个点只能来自于它正上、左上、右上三个方向。

    因此可以选取这三个点中最大的一个作为自己的上一步,再加在自己的权值上。最后对最底下一排的中间三个点的权值求最大值即可。

    这种动态规划的想法很好想代码也比较好实现。这里写一下转移方程式。

    \(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;
    }
    

尾声

这篇题解主要是将两种方法进行一个整理,主要讲述思路,希望对你有帮助。

完结撒花!

posted @ 2026-01-19 17:26  Circle_Table  阅读(2)  评论(0)    收藏  举报