1. 題目來源
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
了不再多闡述什麼了。
對於本題還可以進行矩陣乘法的優化,能將時間複雜度降低到 ,但也是太菜了,先挖個坑,待填。