藍橋杯 2014屆真題 地宮取寶 動態規劃解法

藍橋杯 2014屆真題 地宮取寶 動態規劃解法

題目描述

X 國王有一個地宮寶庫。是 n x m 個格子的矩陣。每個格子放一件寶貝。每個寶貝貼著價值標籤。

地宮的入口在左上角,出口在右下角。

小明被帶到地宮的入口,國王要求他只能向右或向下行走。

走過某個格子時,如果那個格子中的寶貝價值比小明手中任意寶貝價值都大,小明就可以拿起它(當然,也可以不拿)。

當小明走到出口時,如果他手中的寶貝恰好是k件,則這些寶貝就可以送給小明。

請你幫小明算一算,在給定的局面下,他有多少種不同的行動方案能獲得這k件寶貝。

輸入

輸入一行3個整數,用空格分開:n m k (1< =n,m< =50, 1< =k< =12)

接下來有 n 行數據,每行有 m 個整數 Ci (0< =Ci< =12)代表這個格子上的寶物的價值

題解

本題如果直接使用 dfs 暴力搜索,將無法通過測試,但引入記憶化將可以剪枝避免大量的重複計算。我們知道如果一個問題可以分解為多個小問題,且各個小問題之間相互獨立,那麼我們可以使用動態規劃來處理。

1.首先定義 dp 數組

dp[i][j][k][c]: 表示從 \((1, 1)\)走到 \((i,j)\)時,手裡有 \(k\) 件寶貝,且寶貝價值的最大值為 \(c\) 時的行動方案數。

2.找到狀態轉移方程

根據題目知,小明只能向下或者向右行走,則\((i,j)\) 的狀態應從 \((i-1,j)\)\((i,j-1)\) 的狀態轉移過來,有 \(dp[i][j]=dp[i-1][j]\quad OP \quad dp[i][j-1]\),(OP 表示某個操作)。

又知走過某個格子時,如果那個格子中的寶貝價值比小明手中任意寶貝價值都大,小明就可以拿起它(當然,也可以不拿),假設當前格子中寶貝的價值為 cur 則:

若此時拿走寶貝:\(dp[i][j][k][cur]+=dp[i-1][j][k-1][0…cur-1]+dp[i][j-1][k-1][0…cur-1]\)

// 拿走寶貝
for (int c = 0; c < cur; c++) {
    dp[i][j][k][cur] += dp[i - 1][j][k - 1][c] + dp[i][j - 1][k - 1][c];
}

若不拿走寶貝:\(dp[i][j][k][c]+=dp[i-1][j][k][c]+dp[i][j-1][k][c]\),(c = 0, 1, 2 … 12)

// 不拿走寶貝
for (int c = 0; c <= 12; c++) {
    dp[i][j][k][c] += dp[i - 1][j][k][c] + dp[i][j - 1][k][c];
}

3.base case

\((i,j)\) 處寶貝的價值 \(a[i][j]\) 為當 k = 1時,\(dp[i][j][1][a[i][j]]\) 表示只選擇當前寶貝,可知方案數即為從 \((1, 1)\)走到 \((i,j)\) 的方案數。

定義 \(DP[i][j]\) 表示從 \((1, 1)\)走到 \((i,j)\) 的方案數,易知 \(DP[i][j] = DP[i – 1][j] + DP[i][j-1]\)

則有 \(dp[i][j][1][a[i][j]]+=dp[i-1][j][1][a[i-1][j]]+dp[i][j-1][1][a[i][j-1]]\)

// base case
dp[1][1][1][a[1][1]] = 1;
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        dp[i][j][1][a[i][j]] += dp[i-1][j][1][a[i-1][j]] + dp[i][j-1][1][a[i][j-1]];
    }
}

程式碼:

import java.util.Scanner;

public class Main {
    static int MAXN = 50, MAXM = 50;
    static int MAXK = 12, MAXC = 12;
    static long[][][][] dp = new long[MAXN + 1][MAXM + 1][MAXK + 1][MAXC + 1];
    static int[][] a = new int[MAXN + 1][MAXM + 1];
    static int n, m, k, ans = 0, mod = 1000000007;
  	
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        k = sc.nextInt();
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                a[i][j] = sc.nextInt();
        
        // base case
        dp[1][1][1][a[1][1]] = 1;
        for (int i = 1; i <= n; i++) 
            for (int j = 1; j <= m; j++)
                if (!(i == 1 && j == 1)) {
                    dp[i][j][1][a[i][j]] += dp[i-1][j][1][a[i-1][j]] + dp[i][j-1][1][a[i][j-1]];
                    dp[i][j][1][a[i][j]] %= mod;
                }
        
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
        		for (int l = 1; l <= k; l++) {
                    int cur = a[i][j];
                    // 拿走寶貝
                    for (int c = 0; c < cur; c++) {
                        dp[i][j][l][cur] += dp[i-1][j][l-1][c] + dp[i][j-1][l-1][c];
                        dp[i][j][l][cur] %= mod;
                    }
                    // 不拿走寶貝
                    for (int c = 0; c <= MAXC; c++) {
                        dp[i][j][l][c] += dp[i-1][j][l][c] + dp[i][j-1][l][c];
                        dp[i][j][l][c] %= mod;
                    }
                }
        
        for (int c = 0; c <= MAXC; c++) {
            ans += dp[n][m][k][c];
            ans %= mod;
        }
        
        System.out.println(ans);
    }
}

最大時間複雜度\(O(T)=O(i*j*k*c)=O(50*50*12*13)=O(390000)\)