洛谷題單指南-動態規劃2-P1541 [NOIP2010 提高組] 烏龜棋

江城伍月發表於2024-05-08

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

題意解讀:m張卡片,每張卡片數字1-4,不同的卡片排列,導致不同的走法,也產生不同的總分數,求最大分數。

解題思路:

首先想到的是暴力列舉,透過dfs列舉不同的卡片排列,然後不同的排列計算分數,求最大值

有兩種方式實現排列:

1、列舉每一張卡片,根據每張卡片是否已經使用過進行遞迴排列

2、列舉卡片的數字1-4,根據當前數字的卡片是否有剩餘來進行遞迴排列

由於相同數字的卡片有多張,第一種方法的排列勢必導致重複,比如第一張、第二張卡片都是1,用第一種方法列舉會出現“第一張 第二張”“第二張 第一張”兩種情況,而實際上對應的卡片數字都是1 1,因此採用第二種方式進行dfs,儘量減少重複。

30分程式碼:

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

const int N= 400;
int n, m;
int a[N], b[5]; //a記錄所有格子的分數,b[5]記錄1~4卡片的張數
int ans;

//第k張卡片,第x格,當前總分是sum
void dfs(int k, int x,int sum)
{
    if(k == m + 1 && x == n)
    {
        ans = max(ans, sum);
        return;
    }
    for(int i = 1; i <= 4; i++) //遍歷卡片數字1~4可以減少全排列的重複,如果是直接遍歷每張卡片會導致較多的重複,更耗時
    {
        if(b[i] > 0)
        {
            b[i]--;
            dfs(k + 1, x + i, sum + a[x + i]);
            b[i]++;
        }
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    int x;
    for(int i = 1; i <= m; i++) 
    {
        cin >> x;
        b[x]++;
    }
    dfs(1, 1, a[1]);
    cout << ans;

    return 0;
}

DFS有沒有最佳化的可能呢?是有的,可以藉助於記憶化+剪枝

f[i][j]記錄用i個卡走到j格時的得分,這樣每次dfs之前,判斷如果本次i、j對應的f[i][j]已經存在且>=當前的得分,則可以停止dfs,因為在消耗了相同卡片數量且到達相同的格子,如果得分比之前的情況還少,後面肯定大機率是無法超越了。

50分程式碼:

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

const int N= 400, M = 200;
int n, m;
int a[N], b[5]; //a記錄所有格子的分數,b[5]記錄1~4卡片的張數
int ans;
int f[M][N]; //f[i][j]記錄用i個卡走到j的得分

//第k張卡片,第x格,當前總分是sum
void dfs(int k, int x,int sum)
{
    if(f[k][x] >= sum) return; //如果得分比之前的小,不用繼續
    f[k][x] = sum;
    if(k == m + 1 && x == n)
    {
        ans = max(ans, sum);
        return;
    }
    for(int i = 1; i <= 4; i++) //遍歷卡片數字1~4可以減少全排列的重複,如果是直接遍歷每張卡片會導致較多的重複,更耗時
    {
        if(b[i] > 0)
        {
            b[i]--;
            dfs(k + 1, x + i, sum + a[x + i]);
            b[i]++;
        }
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    int x;
    for(int i = 1; i <= m; i++) 
    {
        cin >> x;
        b[x]++;
    }
    dfs(1, 1, a[1]);
    cout << ans;

    return 0;
}

本題的正解應該是動態規劃,但是狀態如何表示呢?

可以考慮最後一步的情況,最後一步,可能走1/2/3/4格,這樣,可以直接把1、2、3、4對應的卡片張數作為狀態!

狀態表示:dp[i][j][k][l]表示使用i張1卡片,j張2卡片,k張3卡片,l張4卡片能得到的最大分數

狀態計算:最後一步可能走1或2或3或4格,則有dp[i][j][k][l] = max(dp[i-1][j][k][l] + a[i*1+j*2+k*3+l*4+1],dp[i][j-1][k][l] + a[i*1+j*2+k*3+l*4+1], dp[i][j][k-1][l] + a[i*1+j*2+k*3+l*4+1]), dp[i][j][k][l-1] + a[i*1+j*2+k*3+l*4+1],i*1+j*2+k*3+l*4+1表示用完i、j、k、l張卡片後走到的位置,注意要判斷i-1,j-1,k-1,l-1非負

初始值:dp[0][0][0][0][0] + a[1] , 一張卡片都不用,初始得分就是第一個格子的分數

結果:dp[b[1]][[b[2]][b[3]][b[4]] ,也就是消耗所有的1,2,3,4卡片能得到的最大分數

100分程式碼:

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

const int N= 400, M = 45;
int n, m;
int a[N], b[5]; //a記錄所有格子的分數,b[5]記錄1~4卡片的張數
int dp[M][M][M][M]; //dp[i][j][k][l]表示使用i張1卡片,j張2卡片,k張3卡片,l張4卡片能得到的最大分數

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    int x;
    for(int i = 1; i <= m; i++) 
    {
        cin >> x;
        b[x]++;
    }
    dp[0][0][0][0] = a[1];
    for(int i = 0; i <= b[1]; i++)
    {
        for(int j = 0; j <= b[2]; j++)
        {
            for(int k = 0; k <= b[3]; k++)
            {
                for(int l = 0; l <= b[4]; l++)
                {
                    int idx = i * 1 + j * 2 + k * 3 + l * 4 + 1; //用完i,j,k,l張卡片後走到的位置
                    if(i > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i-1][j][k][l] + a[idx]);
                    if(j > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j-1][k][l] + a[idx]);
                    if(k > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k-1][l] + a[idx]);
                    if(l > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k][l-1] + a[idx]);
                }
            }
        }
    }
    cout << dp[b[1]][b[2]][b[3]][b[4]];

    return 0;
}

相關文章