標籤:狀態壓縮 動態規劃 矩陣快速冪
題意: 給定n*m的棋盤(1<=N<=10^15, 1<=M<=7),用L型骨牌(田字型任意去掉一個口)完全覆蓋它,問有多少種解。
思路:m的範圍只有1<=M<=7,顯然狀壓DP。但是N的最大值到10^15,只能用快速冪了。
狀態表示:0代表此處留空,1代表此處被填滿。01序列壓縮成一個int型來表示一行的填放情況。(例如:狀態為4,則代表100,即第一列填滿,第二第列三空)
遞推矩陣是長這樣的:
邊界條件:
其中,
t = 2^M
代表將前i-1行填滿,且第i行放置了狀態s時的總方案數。
代表上一行原本放置了狀態s2的前提下,當前行放置骨牌把上一行填滿並使得當前行狀態為s1的方案數。
那麼問題來了,怎麼取得矩陣D呢?
我枚舉上一行狀態s2,對每個s2進行dfs:依次掃描s2的每一位,如果有空位置,則嘗試拜訪某個方向的骨牌(顯然共4種方向)。當把s2所有位置拜滿以後,s1便產生了,那麼讓增加1 (初始為0).
有了矩陣D,天黑都不怕!
顯然有
矩陣快速冪即可,最後輸出就是結果。
題目本身沒多難,都怪我裝壓DP沒學好,做了這題總算弄明白了一些誤區。
明明還有好多事情要忙,但是看到這種東西就是想做,好久沒有這種純粹的感覺了。。
下面附上代碼
#include<cstdio>#include<cstring>#include<iostream>using namespace std;typedef long long LL;const int mod = 1000000007;const int maxn = 130;int off[5]={0,1,1,2,2};int d[maxn][maxn];LL N;int M; //N行 M列int maxs; //總狀態數 1<<Minline void int2arr(int num,bool arr[])//數字轉矩陣{ for(int i=0;i<M;++i,num>>=1) arr[M-i-1] = num&1;}inline int arr2int(bool arr[])//矩陣轉數字{ int num = 0; for(int i=0;i<M;(num<<=1)|=arr[i++]); return num;}bool sbuf[2][10];inline bool check(int cur,int type)//判斷在cur位置是否可以放置type方向的骨牌{ if(type == 1) return sbuf[0][cur]==0 && sbuf[1][cur]==0 && cur+1<M && sbuf[1][cur+1]==0; if(type == 2) return sbuf[0][cur]==0 && sbuf[1][cur]==0 && cur-1>=0 && sbuf[1][cur-1]==0; if(type == 3) return sbuf[0][cur]==0 && sbuf[1][cur]==0 && cur+1<M && sbuf[0][cur+1]==0; if(type == 4) return sbuf[0][cur]==0 && cur+1<M && sbuf[0][cur+1]==0 && sbuf[1][cur+1]==0;}inline void putblock(int cur,int type,int cont)//在cur位置放置type方向的骨牌{ if(type == 1) sbuf[1][cur] = sbuf[1][cur+1] = cont; if(type == 2) sbuf[1][cur] = sbuf[1][cur-1] = cont; if(type == 3) sbuf[1][cur] = cont; if(type == 4) sbuf[1][cur+1] = cont;}void dfs(int cur)//dfs不多解釋{ if(cur >= M) { ++d[arr2int(sbuf[1])][arr2int(sbuf[0])]; return; } if(sbuf[0][cur]==1) dfs(cur+1);//如果當前位置已填,繼續嘗試下一列 for(int i=1;i<=4;++i) if(check(cur,i))//如果當前位置可以放置i方向骨牌 { putblock(cur,i,1);//嘗試放置i方向骨牌 dfs(cur+off[i]);//繼續嘗試後面列 putblock(cur,i,0);//撤銷放置i方向骨牌 }}inline void getd()//計算矩陣D{ maxs = 1<<M; memset(d,0,sizeof(d)); for(int i=0;i<maxs;++i) int2arr(i,sbuf[0]), dfs(0);}inline void matmult(int a[maxn][maxn],int b[maxn][maxn])//矩陣乘法a*=b{ static int c[maxn][maxn]; memset(c,0,sizeof(c)); for(int i=0;i<maxs;++i) for(int j=0;j<maxs;++j) for(int k=0;k<maxs;++k) c[i][j] = (c[i][j] + ((LL)a[i][k] * (LL)b[k][j]) % mod) % mod; memcpy(a,c,sizeof(c));}void matpower(int a[maxn][maxn],LL p)//矩陣快速冪a^p{ int ans[maxn][maxn]; memset(ans,0,sizeof(ans)); for(int i=0;i<maxs;++i) ans[i][i]=1; //ans=1 for(;p;p>>=1,matmult(a,a)) if(p&1) matmult(ans,a);//ans*=a; //a*=a; memcpy(a,ans,sizeof(ans)); //return ans}int main(){ cin>>N>>M; getd(); matpower(d,N); cout<< d[ maxs-1 ][ maxs-1 ] <<endl; return 0;}/*各個方向的骨牌及其對應編號 1 * ** 2 * ** 3 ** * 4 ** **/
軟體能力認證題---拼圖(狀態壓縮DP+矩陣快速冪)