【題目解析】藍橋杯23國賽C++中高階組 - 鬥魚養殖場

Macw發表於2024-10-09

【題目解析】藍橋杯23國賽C++中高階組 - 鬥魚養殖場

題目連結跳轉:點選跳轉

前置知識:

  1. 瞭解過基本的動態規劃。
  2. 熟練掌握二進位制的位運算。

題解思路

這是一道典型的狀壓動態規劃問題。設 \(dp_{i, j}\) 表示遍歷到第 \(i\) 行的時候,當前行以 \(j_{(base2)}\) 的形式排列烏龜可以構成的方案數。

對於每一行的方案,我們可以用一個二進位制來表示。例如二進位制數字 \(10100\),表示有一個橫向長度為 \(5\) 的場地中,第 \(1, 3\) 號位置分別放置了一隻小烏龜。因此,每一種擺放狀態都可以用一個二進位制數字來表示。我們也可以透過遍歷的方式來遍歷出二進位制的每一種擺放狀態。

首先,我們預處理出橫排所有放置烏龜的合法情況。根據題意,兩個烏龜不能相鄰放置,因此在二進位制中,不能有兩個 \(1\) 相鄰。如何預處理出這種情況呢?我們可以使用位運算的方法:

如果存在一個二進位制數字有兩個 \(1\) 相鄰,那麼如果我們對這個數字 \(x\) 進行位運算操作 (x << 1) & x 的結果或 (x >> 1) & x 的結果必定大於等於 \(1\)。我們透過把這種情況排除在外。同時,我們還需要注意有些格子中不能放置烏龜。這一步也可以透過二進位制的方法預處理掉,如果網箱在第 \(i\) 一個格子中不能放置烏龜,那麼在列舉所有方案數的時候直接忽略掉第 \(i\) 位為 \(1\) 的情況即可。

接下來如何保證上下兩行的烏龜不衝突?假如上一行的擺放狀態是 \(y\),當前行的擺放狀態為 \(j\),如果 i & j 的結果大於等於 \(1\),也可以證明有兩個數字 \(1\) 在同一位置上。因此我們也需要把這種情況排除在外。

綜上所述,我們可以得出狀態轉移方程:\(dp_{i, j} = dp_{i, j} + dp_{i-1, k}\)。其中,\(j\)\(k\) 表示所有橫排合法的方案。答案就是 \(\mathtt{ANS} = \sum_{j=0}^{2^M-1}{dp_{N, j}}\)

狀態的初始化也很簡單,另 \(dp_{0, 0} = 1\)​,表示一隻烏龜都不放有一種擺放方案。

時間複雜度

透過觀察上述程式碼,在列舉所有狀態和轉移狀態的時候有三層迴圈,分別是列舉當前行、列舉當前行的合法擺放情況以及列舉上一行的擺放情況。因此總時間複雜度約為 \(O(n \times 2^M \times 2^M) = O(n \times 2^{M^2}) = O(n \times 4^M)\)。但由於合法的擺放數量遠遠少於 \(2^M\),因此實際情況下程式執行的速度會快許多。

程式碼實現

本題的程式碼實現如下。在輸出的時候需要減一,因為不放置也是一種合法情況,根據題目要求需要把這一合法情況排除。

#include <iostream>
using namespace std;

const int MOD = 1e9+7;
int n, m, ans;
int arr[505][505];
// 所有橫排合法的情況。
int terrain[505];
int ok[1050], cnt;
int dp[505][1050];

int main(){
    cin >> n >> m;
    for (int i=1; i<=n; i++){
        for (int j=1; j<=m; j++){
            cin >> arr[i][j];
        }
    }
    
    // 預處理非法地形。
    for (int i=1; i<=n; i++){
        for (int j=1; j<=m; j++){
            terrain[i] = (terrain[i] << 1) + !arr[i][j];
        }
    }
    
    // 預處理出所有橫排的合法情況。
    for (int i=0; i<(1<<m); i++){
        if (((i<<1)|(i>>1)) & i) continue;
        ok[++cnt] = i;
    }
    dp[0][1] = 1;

    // 列舉。
    for (int i=1; i<=n; i++){
        for (int s1=1; s1<=cnt; s1++){  // 列舉當前行。
            if (ok[s1] & terrain[i]) continue;
            for (int s2=1; s2<=cnt; s2++){  // 列舉上一行。
                if (ok[s2] & terrain[i-1]) continue;
                if (ok[s1] & ok[s2]) continue;
                dp[i][s1] = (dp[i][s1] + dp[i-1][s2]) % MOD;
            }
        }
    }

    // 統計答案。
    int ans = 0;
    for (int i=1; i<=cnt; i++)
        ans = (ans + dp[n][i]) % MOD;
    
    cout << ans - 1 << endl;
    return 0;
}

本題的 Python 程式碼如下,Python 可以透過本題的所有測試點:

MOD = int(1e9 + 7)
n, m, ans = 0, 0, 0
arr = [[0] * 505 for _ in range(505)]
terrain = [0] * 505
ok = [0] * 1050
dp = [[0] * 1050 for _ in range(505)]
cnt = 0

def main():
    global n, m, cnt, ans
    
    # 輸入 n 和 m
    n, m = map(int, input().split())
    
    # 輸入 arr 陣列
    for i in range(1, n + 1):
        arr[i][1:m + 1] = map(int, input().split())
    
    # 預處理非法地形
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            terrain[i] = (terrain[i] << 1) + (1 - arr[i][j])
    
    # 預處理出所有橫排的合法情況
    for i in range(1 << m):
        if ((i << 1) | (i >> 1)) & i:
            continue
        cnt += 1
        ok[cnt] = i
    
    dp[0][1] = 1
    
    # 列舉
    for i in range(1, n + 1):
        for s1 in range(1, cnt + 1):  # 列舉當前行
            if ok[s1] & terrain[i]:
                continue
            for s2 in range(1, cnt + 1):  # 列舉上一行
                if ok[s2] & terrain[i - 1]:
                    continue
                if ok[s1] & ok[s2]:
                    continue
                dp[i][s1] = (dp[i][s1] + dp[i - 1][s2]) % MOD
    
    # 統計答案
    ans = 0
    for i in range(1, cnt + 1):
        ans = (ans + dp[n][i]) % MOD
    
    print(ans - 1)

if __name__ == "__main__":
    main()

再提供一個暴力解法用於對拍:

#include <iostream>
using namespace std;

const int MOD = 1e9+7;
int n, m, ans;
int arr[505][505];
int dx[] = {0, 1, -1, 0};
int dy[] = {1, 0, 0, -1};

// 深度優先搜尋 Brute Force
void dfs(int x, int y){
    if (x > n) {
        ans += 1;
        ans %= MOD;
        return ;
    }
    if (y > m){
        dfs(x+1, 1);
        return ;
    }
    if (arr[x][y] == 0){
        dfs(x, y+1);
        return ;
    }
    // 不放魚
    dfs(x, y+1);

    // 放魚
    for (int i=0; i<4; i++){
        int cx = x + dx[i];
        int cy = y + dy[i];
        if (cx < 1 || cy < 1 || cx > n || cy > m) continue;
        if (arr[cx][cy] == 2) return ;
    }
    arr[x][y] = 2;
    dfs(x, y+1);
    arr[x][y] = 1;
    return ;
}

int main(){
    cin >> n >> m;
    for (int i=1; i<=n; i++){
        for (int j=1; j<=m; j++){
            cin >> arr[i][j];
        }
    }
    // dfs 暴力
    dfs(1, 1);
    cout << ans-1 << endl;
    return 0;
}

相關文章