[LeetCode 周賽184] 4. 給 N x 3 網格圖塗色的方案數(遞推、狀壓dp、數學、巧妙解法)

1. 題目來源

鏈接:5383. 給 N x 3 網格圖塗色的方案數

2. 題目說明

在這裏插入圖片描述
在這裏插入圖片描述

3. 題目解析

方法一:遞推+狀壓dp+巧妙解法

就爲啥我還能把樣例 1 的圖示看成 3 列呢,結果就把題意理解錯了…糾結了好久…真吐了。

主要思想爲遞推、狀壓 dp,下面簡單理下思路:

  • 狀態定義:dp[i][bits] 已經填色到第 i 行,i 行填色方案爲 bits 的方案數。在此要注意 bits 爲 6 位二進制,通過簡單位運算即可將其切割爲三個 [0,4),即用 01 位,23 位, 45 位表示三個格子的 3 種顏色情況,並將數字 3 廢棄,利用 0,1,2 表示這三種顏色
  • 首先對第一行進行初始化,由於是 6 位二進制,那麼就會產生 64 種可能填色的情況,遍歷這 64 種情況即可。判斷時有兩個條件需要滿足:3 不在顏色種類內,並且相鄰顏色不能相等。滿足這兩個條件,則將第一行 bits 填色方案結果初始化爲 1
  • 然後遞推計算剩下的所有行即可,當前行也會產生 64 種情況,遍歷計算。在此需要注意,若當前行的某種填色方案已經合法了,就需要判斷是否與上一行的填色方案產生了衝突,即判斷同列值是否相等
  • 並且值得注意的是,若前一行的填色方案不衝突但是填色方案爲 0 時,就直接 continue,尋找下一個填色方案。爲什麼呢?在此有兩種情況需要考慮
    • 該方案不合法,所有無法通過之前的遞推進行轉移
    • 取模後變成 0,不會對結果產生影響,直接 continue 即可
  • 再將當前行的某種填色方案與前一行的填色方案狀態進行累加即可

即這個狀態壓縮遞推就是:

  • 先枚舉行
  • 再枚舉該行的所有填色方案
  • 該行當前的填色方案是否合法
  • 若合法則檢查前一行是否會與當前行的填色方案是否產生衝突
  • 最後累加方案數即可

參見代碼如下:

// 執行用時 :392 ms, 在所有 C++ 提交中擊敗了100.00%的用戶
// 內存消耗 :8.4 MB, 在所有 C++ 提交中擊敗了100.00%的用戶

#define LL long long
const LL MOD = 1e9+7;
LL dp[5050][65];

class Solution {
public:
    int get(int v, int c){
        return (v >> (c * 2)) % 4;
    }

    int numOfWays(int n) {
        for (int s = 0; s < 64; s++){
            int a = get(s, 0), b = get(s, 1), c = get(s, 2);
            dp[1][s] = 0;
            if (a == 3 || b == 3 || c == 3) continue;
            if (a == b || b == c) continue;
            dp[1][s] = 1;
        }

        for (int i = 2; i <= n; i++) {
            for (int cur = 0; cur < 64; cur++) {
                dp[i][cur] = 0;
                int na = get(cur, 0), nb = get(cur, 1), nc = get(cur, 2);
                if (na == 3 || nb == 3 || nc == 3) continue;
                if (na == nb || nb == nc) continue; 
                for (int prev = 0; prev < 64; prev++) {
                    int pa = get(prev, 0), pb = get(prev, 1), pc = get(prev, 2);
                    if (pa == na || pb == nb || pc == nc) continue;
                    if (dp[i - 1][prev] == 0) continue;
                    dp[i][cur] = (dp[i][cur] + dp[i - 1][prev] ) % MOD;
                }
            }
        }
        
        LL ans = 0;
        for (int s = 0; s < 64; s++){
            ans = (ans + dp[n][s]) % MOD;
        }
        return ans;
    }
};

註釋版。

參見代碼如下:

#define LL long long
const LL MOD = 1e9+7;
LL dp[5050][65];
// dp[i][bits] 已經填色到第i行,i行填色方案爲bits的方案數
// bits爲6位二進制,將其切割爲三個[0,4),將3廢棄,利用0,1,2表示三種顏色

class Solution {
public:
    int get(int v, int c){
        return (v >> (c * 2)) % 4;
    }

    int numOfWays(int n) {
        // 初始化i=1第一行
        for (int s = 0; s < 64; s++){
            int a = get(s, 0), b = get(s, 1), c = get(s, 2);
            dp[1][s] = 0;
            // 3不在顏色種類內
            if (a == 3 || b == 3 || c == 3) continue;
            // 相鄰的顏色不能相等
            if (a == b || b == c) continue;
            dp[1][s] = 1;
        }
        // 遞推計算剩下的所有行
        for (int i = 2; i <= n; i++) {
            for (int cur = 0; cur < 64; cur++) {
                // 當前行填cur方法
                dp[i][cur] = 0;
                int na = get(cur, 0), nb = get(cur, 1), nc = get(cur, 2);
                if (na == 3 || nb == 3 || nc == 3) continue;
                if (na == nb || nb == nc) continue; 
                // 當前行填cur合法,查看上一行顏色是否有衝突
                for (int prev = 0; prev < 64; prev++) {
                    int pa = get(prev, 0), pb = get(prev, 1), pc = get(prev, 2);
                    if (pa == na || pb == nb || pc == nc) continue;
                    // dp數組在i-1行方案爲prev時方案數爲0即不存在這種方案,在此有兩種情況導致:
                    // 1. prev該方案不合法,所有無法通過之前的遞推進行轉移
                    // 2. 取模後變成0,不會產生影響,直接continue即可
                    if (dp[i - 1][prev] == 0) continue;
                    dp[i][cur] = (dp[i][cur] + dp[i - 1][prev] ) % MOD;
                }
            }
        }
        
        LL ans = 0;
        for (int s = 0; s < 64; s++){
            ans = (ans + dp[n][s]) % MOD;
        }
        return ans;
    }
};

方法二:數學+巧妙解法

來自題解區 1 號大佬,tql數學解決非常快樂

在此我就拋 link 了不再多闡述什麼了。

對於本題還可以進行矩陣乘法的優化,能將時間複雜度降低到 O(nlogn)O(nlogn),但也是太菜了,先挖個坑,待填。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章