[AcWing], 蒙德里安的夢想
題目來源
問題分析
就是將一個n * m
的二維矩陣,分成若干個1 * 2
的方格,有多少種分配方式(完全分配)
可以對放置的方式進行模擬,先放置橫著的1* 2
方格,再放置豎著的2 * 1
方格。那麼擺放的小方格方案數
等價於 橫著擺放的小方格方案數
,因為當橫著合法
擺放的方格確定後,豎著擺放的方式就已經確定了,直接內嵌。
他的資料範圍為1~11
,暗示可以使用二進位制來進行操作,狀態壓縮。那麼我們選擇對每一列的狀態用一個二進位制進行表示,噹噹前行所在的bit
為0時,表示該位置沒有放置;bit
為1時,表示當前位置已經進行了放置。
列號 | 狀態表示 |
---|---|
0 | 0001 |
1 | 1001 |
2 | 1010 |
3 | 0010 |
所使用的變數
預處理合法的擺放方式
所以,我們在判斷橫著擺放方格的時候,就需要提前判斷合法的擺放方式,防止豎著的方格放完後,不能完全鋪滿整個矩陣。
那麼預處理的方法就是,對於每一種狀態(二進位制),然後依次遍歷每一行,在遍歷的途中需要記錄的就是連續的沒有放置的方格數(連續的0),當出現連續的0位奇數個時,表示該列的這種狀態是不合法的。
動態規劃
在動態規劃中,我們從每一列開始遍歷,然後從0 ~ 2^n
依次遍歷每一個狀態。對於每一個j狀態,我們需要找到一個他可以和哪個狀態進行轉換。
那麼對於當前列在遍歷時的狀態,我們需要知道這個狀態可以在前面的哪一個狀態的情況後插入。如果前面當前行i-1
是1,表示現在這一行i
是1*2
方格的後半段,所以對於當前狀態是不能插入的。
在判斷當前狀態是否合法時,可以使用 &
來操作,如果結果為0,表示沒有衝突。
除此之外,之前還做了一次預處理,判斷當前狀態是否合法。那麼這個合法的狀態就需要使用|
來進行操作。
這樣會出現一個小問題,就是這樣問題的時間複雜度為O( 11 * 2^n * 2^n )
,那麼對於最壞的情況下,結果為 11 * 2^11 * 2^11
近似於 5 * 10^7
,這是一個臨界點,在超時的邊緣徘徊。
((i & j) == 0 && st[i | j]
這兩個條件換個位置後,就會超時)
所以我們可以將找狀態集的過程提取出來,減少在三重迴圈中的判斷次數。
初始化狀態集
將結果儲存在一個vector
的可變陣列中
結果
棋盤一共有0 ~ m-1列
f[i][j]
表示前i-1列
的方案數已經確定,從i-1列
伸出,並且第i列的狀態是j的所有方案數f[m][0]
表示前m-1列
的方案數已經確定,從m-1列
伸出,並且第m列
的狀態是0
的所有方案數
也就是m列
不放小方格,前m-1列
已經完全擺放好並且不伸出來的狀態
完整程式碼
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 12, M = 1 << N; // 1-11 列,2^11 個狀態
ll f[N][M]; // f[i][j] 從第i-1列伸出,到i列,狀態為j, j 的二進位制位為1表示當前行放置方格,為0表示不放置
vector<int> state[M]; // 每一種狀態,對應可以放置的狀態
bool st[M]; // 是否可以成功轉移,j 狀態放置 k狀態後 是否合法
int n, m;
int main() {
while(cin >> n >> m, n || m) {
// 初始化
for(int i = 0; i < (1 << n); i ++) {
int cnt = 0; // 前面連續0的數量
bool is_valid = true;
for(int j = 0; j < n; j ++) {
if((i >> j) & 1) { // 1 放置
if(cnt & 1) { // 前面連續0位奇數,不合法
is_valid = false;
break;
}
cnt = 0;
} else { // 0 不放置,連續0 + 1
cnt++;
}
}
if(cnt & 1) is_valid = false; // 餘下0位奇數個,不合法
st[i] = is_valid;
}
for(int i = 0; i < (1 << n); i ++) {
state[i].clear();
for(int j = 0; j < (1 << n); j ++)
if((i & j) == 0 && st[i | j]) state[i].push_back(j); // 對於狀態i來說,可以插入的狀態為j
}
memset(f,0x00,sizeof(f));
f[0][0] = 1;
for(int i = 1; i <= m; i ++)
for(int j = 0; j < (1 << n); j++)
for(auto& k : state[j]) f[i][j] += f[i - 1][k];
cout << f[m][0] << endl;
}
return 0;
}
相關文章
- ACwing291. 蒙德里安的夢想
- 【夢想的聲音】
- 關於夢想
- “夢想江湖,從新出發”《新夢想世界》正式開啟
- 小瓶子大夢想 物理闖關遊戲《瓶子先生和他的夢想》發售遊戲
- 夢想天空分外藍
- 夢想、理想與妄想
- 你有夢想嗎?華為雲學院助你實現夢想
- Logic Pro:音樂家的夢想工具
- 夢想是怎樣的顏色?.txt
- 使用 tmux 建立你的夢想主控臺UX
- 建築師——由來已久的夢想
- CAD夢想畫圖中的“延伸命令”
- CAD夢想畫圖中的“分解命令”
- CAD夢想畫圖2021.09.04更新
- CAD夢想畫圖操作介面
- CAD夢想畫圖---雲線
- 雲網互聯超越夢想
- 北上廣的夢想比不上新一線的戶口?
- CAD夢想畫圖中的“熱鍵快捷命令”
- CAD夢想畫圖中的“線型設定”
- 小學課文 交換夢想
- 夢想CMS(lmxcms)1.4 簡要分析
- 夢想CAD控制元件 2021.09.05更新控制元件
- 創夢天地與華為達成鴻蒙合作鴻蒙
- 鴻蒙系統和安卓的區別 鴻蒙系統是基於安卓嗎鴻蒙安卓
- Acwing4244牛的比賽
- 實現雲原生應用程式可移植的夢想
- 專業遊戲開發者眼中的《夢想世界》遊戲開發
- 達夢安裝
- ChatMoney:AI看病,私人醫生不是夢想!AI
- CAD夢想畫圖產品簡介
- 回顧2014展望2015,放飛夢想~
- CAD夢想畫圖--審圖示記
- 我們離夢想還有多遠
- “沒有了夢想”的Uber,是如何在AI上折戟沉沙的?AI
- 原本只是單純的技術 卻被披上夢想的外衣
- Xcode:助力Mac開發者實現夢想的舞臺XCodeMac