洛谷題單指南-動態規劃2-P1004 [NOIP2000 提高組] 方格取數

江城伍月發表於2024-04-29

原題連結:https://www.luogu.com.cn/problem/P1004

題意解讀:從起點走到終點,走兩次,計算最大路徑和,第一次走過的點數值變為0。

解題思路:

直觀上思考,

可以先從起點走到終點,計算最大路徑和,並記錄走過的所有點,然後把所有點的數值置為0,

再從起點走到終點,計算最大路徑和,

把兩次的最大路徑和加起來即可,

而從起點到終點的最大路徑和類似於數字三角形問題

設dp[i][j]表示從起點走到(i,j)的最大路徑和,有dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + a[i][j]

下面實現程式碼:

80分程式碼:

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

struct point
{
    int x, y;
};

const int N = 15;
int a[N][N];
int dp1[N][N], dp2[N][N];
point from[N][N]; //記錄每個點從哪個點走過來
int n;

int main()
{
    cin >> n;
    int x, y, z;
    while(cin >> x >> y >> z)
    {
        if(x == 0 && y == 0 && z == 0) break;
        a[x][y] = z;
    }

    //走第一遍
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if(dp1[i-1][j] >= dp1[i][j-1]) from[i][j] = {i - 1, j};
            else from[i][j] = {i, j - 1};
            dp1[i][j] = max(dp1[i-1][j], dp1[i][j-1]) + a[i][j];
        }
    }
    //將起點到終點路徑中的點數字清0
    int ti = n, tj = n;
    a[1][1] = 0;
    while(ti != 0 || tj != 0)
    {
        a[ti][tj] = 0;
        point p = from[ti][tj];
        ti = p.x, tj = p.y;
    }
    //走第二遍
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            dp2[i][j] = max(dp2[i-1][j], dp2[i][j-1]) + a[i][j];
        }
    }
    cout << dp1[n][n] + dp2[n][n];
    return 0;
}

為什麼只能得部分分?

原因在於分開兩次計算,每次計算路徑最大值都是區域性最優,且第一次計算最大值之後會對第二次計算最大值造成影響,不符合DP的特性。

那麼,如何保證兩次路徑總和最大?可以從起點同時分兩路走到終點,保證中間的每一步路徑之和都是最大的,這樣到終點的路徑之和也是最大的!

設dp[i][j][k][l]表示從起點同步走到(i,j)(k,l)時,兩條路徑之和的最大值

那麼,考慮最後一步,可能有四種組合:

1、從(i-1, j)走到(i, j),從(k-1, l)走到(k, l),狀態轉移表示為dp[i][j][k][l] = dp[i-1][j][k-1][l] + a[i][j] + a[k][l]

2、從(i, j-1)走到(i, j),從(k-1, l)走到(k, l),狀態轉移表示為dp[i][j][k][l] = dp[i][j-1][k-1][l] + a[i][j] + a[k][l]

3、從(i-1, j)走到(i, j),從(k, l-1)走到(k, l),狀態轉移表示為dp[i][j][k][l] = dp[i-1][j][k][l-1] + a[i][j] + a[k][l]

4、從(i, j-1)走到(i, j),從(k, l-1)走到(k, l),狀態轉移表示為dp[i][j][k][l] = dp[i][j-1][k][l-1] + a[i][j] + a[k][l]

注意:由於第一次走過的點第二次變為0,所以在這裡同步走時可以認為如果(i,j)(k,l)重合,即i == k && j == l,則權值只加一次

所以if(i == k && j == l) dp[i][j][k][l] -= a[i][j]

對四種情況取max即可。

100分程式碼:

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

const int N = 15;
int a[N][N];
int dp[N][N][N][N]; //dp[i][j][k][l]表示從起點同步走到(i,j)(k,l)時,兩條路徑之和的最大值
int n;

int main()
{
    cin >> n;
    int x, y, z;
    while(cin >> x >> y >> z)
    {
        if(x == 0 && y == 0 && z == 0) break;
        a[x][y] = z;
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            for(int k = 1; k <= n; k++)
            {
                for(int l = 1; l <= n; l++)
                {
                    dp[i][j][k][l] = max(max(dp[i-1][j][k-1][l], dp[i][j-1][k-1][l]), max(dp[i-1][j][k][l-1], dp[i][j-1][k][l-1])) + a[i][j] + a[k][l];
                    if(i == k && j == l) dp[i][j][k][l] -= a[i][j]; //由於第一次走過的點第二次變為0,所以在這裡同步走時可以認為如果(i,j)(k,l)重合,即i == k && j == l,則權值只加一次
                }
            }
        }
    }
    cout << dp[n][n][n][n];
    
    return 0;
}

相關文章