CSP历年复赛题-P7074 [CSP-J2020] 方格取数

原题链接:https://www.luogu.com.cn/problem/P7074

题意解读:从起点走到终点,可以向上、向下、向右,求经过所有格子数之和最大值。

解题思路:

1、DFS暴搜

从起点开始,dfs所有到终点的路径和,求最大值,注意要回溯。

20分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int dx[3] = {-1, 1, 0};
int dy[3] = {0, 0, 1};
int n, m;
int a[N][N];
bool vis[N][N];
long long ans = LLONG_MIN;

void dfs(int x, int y, long long sum)
{
    if(x == n && y == m)
    {
        ans = max(ans, sum);
        return;
    }
    for(int i = 0; i < 3; i++)
    {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if(nx < 1 || nx > n || ny < 1 || ny > m || vis[nx][ny]) continue;
        vis[nx][ny] = true;
        dfs(nx, ny, sum + a[nx][ny]);
        vis[nx][ny] = false;
    }

}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> a[i][j];
    
    vis[1][1] = true;
    dfs(1, 1, a[1][1]);
    cout << ans;
    return 0;
}

2、DFS+剪枝

由于是求最大值,那么很显然,如果一个位置之前走过,可以保存当时的值,下一次再走如果比之前保存的值还小,就不要再dfs下去了。

由于走到每一个位置时的状态有三种:从上边、从下边、从左边,所以在保存到某个为止的值定,使用三个参数:x,y,dir,分别表示x,y坐标,以及从什么方向过来,定义long long f[N][N][3]保存状态即可。

35分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int dx[3] = {-1, 1, 0};
int dy[3] = {0, 0, 1};
int n, m;
int a[N][N];
bool vis[N][N];
long long f[N][N][3];
long long ans = LLONG_MIN;

void dfs(int x, int y, int dir, long long sum)
{
    if(f[x][y][dir] > sum) return;
    f[x][y][dir] = sum;
    if(x == n && y == m)
    {
        ans = max(ans, sum);
        return;
    }
    for(int i = 0; i < 3; i++)
    {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if(nx < 1 || nx > n || ny < 1 || ny > m || vis[nx][ny]) continue;
        vis[nx][ny] = true;
        dfs(nx, ny, i, sum + a[nx][ny]);
        vis[nx][ny] = false;
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> a[i][j];
    
    memset(f, -0x3f, sizeof(f));
    vis[1][1] = true;
    dfs(1, 1, 0, a[1][1]);
    cout << ans;
    return 0;
}

3、动态规划

考虑某一个点(i, j),可能从上边(i-1, j)、下边(i+1, j)、左边(i, j-1)转移过来,i既可以从i-1递推,也可以由i+1递推,这样一来就无法通过一轮遍历递推出来,必然需要正向、反向两次递推!

那么,在定义状态的时候,就需要考虑方向这个因素。

状态定义:

设a[][]表示方格的数

设f[i][j][0]表示从起点(1, 1)到(i, j),且最后一次是从上边转移的所有数之和最大值

设f[i][j][1]表示从起点(1, 1)到(i, j),且最后一次是从下边转移的所有数之和最大值

设f[i][j][2]表示从起点(1, 1)到(i, j),且最后一次是从左边转移的所有数之和最大值

状态转移:

有三种转移方式:

f[i][j][0] = max(f[i-1][j][0], f[i-1][j][2]) + a[i][j]

f[i][j][1] = max(f[i+1][j][1], f[i+1][j][2]) +a[i][j]

f[i][j][2] = max(f[i][j-1][0], max(f[i][j-1][1], f[i][j-1][2])) +a[i][j]

由于i既可以依赖i-1,又可以依赖i+1,因此需要两次递推

第一次从左到右、从上到下遍历递推,计算f[i][j][0], f[i][j][1]

第二次从左到右、从下到下遍历递推,计算f[i][j][1], f[i][j][2]

需要注意下标不能超出棋盘范围

初始化:

f[1][1][0] = f[1][1][1] = f[1][1][2] = a[1][1]

结果:

max(f[n][m][0], f[n][m][2])

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int n, m;
int a[N][N];
long long f[N][N][3];

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> a[i][j];
    
    memset(f, -0x3f, sizeof(f));

    f[1][1][0] = f[1][1][1] = f[1][1][2] = a[1][1];
    for(int j = 1; j <= m; j++)
    {
        for(int i = 1; i <= n; i++)
        {
            if(i - 1 >= 1) f[i][j][0] = max(f[i-1][j][0], f[i-1][j][2]) + a[i][j];
            if(j - 1 >= 1) f[i][j][2] = max(f[i][j-1][0], max(f[i][j-1][1], f[i][j-1][2])) + a[i][j];
        }
        for(int i = n; i >= 1; i--)
        {
            if(i + 1 <= n) f[i][j][1] = max(f[i+1][j][1], f[i+1][j][2]) + a[i][j];
            if(j - 1 >= 1) f[i][j][2] = max(f[i][j-1][0], max(f[i][j-1][1], f[i][j-1][2])) + a[i][j];
        }
    }
           
    cout << max(f[n][m][0], f[n][m][2]);

    return 0;
}

 

posted @ 2024-06-14 15:34  五月江城  阅读(194)  评论(0编辑  收藏  举报