狀態壓縮與動態規劃(DP)---程式設計之美---瓷磚覆蓋地板---POJ2411
一、狀態壓縮
從狀態壓縮的特點來看,這個演算法適用的題目符合以下的條件:
1.解法需要儲存一定的狀態資料(表示一種狀態的一個數據值) ,每個狀態資料通常情況下是可以通過 2 進位制來表示的。這就要求狀態資料的每個單元只有兩種狀態,比如說棋盤上的格子,放棋子或者不放,或者是硬幣的正反兩面。這樣用 0 或者 1 來表示狀態資料的每個單元,而整個狀態資料就是一個一串 0 和 1 組成的二進位制數。2.解法需要將狀態資料實現為一個基本資料型別,比如 int, long 等等, 即所謂的狀態壓縮。狀態壓縮的目的一方面是縮小了資料儲存的空間,另一方面是在狀態對比和狀態整體處理時能夠提高效率。這樣就要求狀態資料中的單元個數不能太大,比如用 int 來表示一個狀態的時候,狀態
二、動態規劃
如果說狀態壓縮是資料結構的話,那麼動態規劃應該是演算法了。題目通過動態規劃來解通常有兩個動機,第一是利用遞迴的重疊子問題,進行記憶話求解,即遞迴法的優化。第二是把問題看作多階段決策的過程來求解問題。在狀態壓縮動態規劃中我們討論的是第二種動機。
多階段決策過程求解問題的動態規劃最重要的是劃分階段和找到狀態轉移方程。 對於劃分階段,是根據不同階段之間的獨立性來劃分,通常會用狀態陣列的第一個下標來記錄這個階段的標記(比如 01 揹包問題中的狀態陣列第一個下標為物品的個數,棋盤放棋子問題中的狀態陣列的第一個下標為棋盤的行數等等)。另一個重要的便是狀態轉移方程,狀態轉移方程是遞推時得到一個狀態資料的重要根據。通常情況下狀態陣列的除了第一個下標以外都是表示狀態資料的,而狀態陣列的值是和所求結果緊密結合的。在後面的幾個例題中會重點說明
狀態轉移方程。
當狀態壓縮和動態規劃結合的時候便形成了一類問題的一種演算法,即狀態壓縮動態規劃的演算法。這種演算法最常見在棋盤問題上或者網格問題上,因為這一類問題的狀態資料的單元較少,可以通過狀態壓縮來對當前棋盤或者網格的狀態進行處理。
三、例項
說了這麼多應該上菜啦
題:用1x2的瓷磚覆蓋NxM的地板,問總共有多少種方案
首先應該滿足N*M是偶數,這是一個大前提,因為每一個小瓷磚的面積是偶數。
分析:總體思路---先將第一行所有狀態進行遍歷,並採用資料壓縮的方法記錄有效狀態,這裡用0表示沒有橫向瓷磚覆蓋---有下一行的瓷磚縱向覆蓋,1表示有瓷磚覆蓋---橫向或縱向,用一個二維陣列來記錄能夠到達當前狀態的方案數,行數作為一維索引,狀態值作為二維索引;然後從i-1行的有效狀態出發,遍歷並判斷第i行的所有狀態是否與i-1行的有效狀態相容,如果相容,則將當前遍歷的i行k狀態的可到達方案數加上與其相容的i-1行j狀態的可到達方案數。
相關說明:
是否覆蓋---10101 則i行0,2,4位置被覆蓋,1,3位置未被覆蓋,需要下一行的瓷磚縱向覆蓋
相容性------i-1行00110與i行11111相容,與i行10111不相容
該問題裡狀態轉移就是相容性判斷
code
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int w,h;
bool TestFirstLine(int n)
{
int i=0;
while(i<w)
{
if( n & (0x1<<i) )
{
if(i==w-1 || ( n &0x1<<(i+1) )==0 )
{
return false;
}
i+=2;
}
else
{
i++;
}
}
return true;
}
bool TestCompitable(int cur_s,int pre_s) // test if status (i, cur_s) and (i-1, pre_s) is compatable
{
int i=0;
while(i<w)
{
if(cur_s & (0x1<<i) )
{
if( !( pre_s & (0x1<<i) ) )
{
i++;
}
else if( ( pre_s & (0x1<<(i+1) )) && ( cur_s & 0x1<<(i+1) ) && i!=w-1 )
{
i+=2;
}
else
{
return false;
}
}
else
{
if( pre_s & (0x1<<i) )
{
i++;
}
else
{
return false;
}
}
}
return true;
}
int main(void)
{
int i,j,k;
int nStates;
long** s;
printf("Please enter the width of the floor:\n");
printf("-----");
scanf("%d",&w);
printf("Please enter the height of the floor:\n");
printf("-----");
scanf("%d",&h);
if((w*h)%2)
{
printf("The program number is: 0\n");
system("pause");
return 0;
}
if(w>h)
{
std::swap(w,h);
}
nStates=(1<<w);
s=(long**)malloc(h*sizeof(long*));
for(i=0;i<h;i++)
{
s[i]=(long*)malloc(nStates*sizeof(long));
memset(s[i],0,nStates*sizeof(long));
}
for(i=0;i<nStates;i++)
{
if(TestFirstLine(i))
{
s[0][i]=1;
}
}
for(i=1;i<h;i++)
{
for(j=0;j<nStates;j++) //cur-line,state index
{
for(k=0;k<nStates;k++) //pre-line,state index
{
if(s[i-1][k]!=0) //pre-line has the state
{
if(TestCompitable(j,k))
{
s[i][j]+=s[i-1][k]; //compatible
}
}
}
}
}
printf("The program number is: %d\n",s[h-1][nStates-1]);
system("pause");
return 0;
}