1. 程式人生 > >棋盤問題(狀態壓縮例題1)

棋盤問題(狀態壓縮例題1)

題目 有一個NM(N<=5,M<=1000)的棋盤,現在有12及2*1的小木塊無數個,要蓋滿整個棋盤,有多少種方式?答案只需要mod1,000,000,007即可。

分析 這道題在很久之前我就看到過,之前自己沒有打出來,然後身邊的隊友用的搜尋過的。然後最近初學狀壓DP,再次接觸到,看了很多例題都沒怎麼理解狀壓DP,直到看到這道題,感覺稍微懂了一點。以下放出分析:

在這道題目中,N和M的範圍本應該是一樣的,但實際上,N和M的範圍卻差別甚遠,對於這種題目,首先應該想到的就是,正確演算法與這兩個範圍有關!N的範圍特別小,因此可以考慮使用狀態壓縮動態規劃的思想:

假設第一列已經填滿,則第二列的擺設方式,只與第一列對第二列的影響有關。同理,第三列的擺設方式也只與第二列對它的影響有關。那麼,使用一個長度為N的二進位制數state來表示這個影響,例如:4(00100)就表示了圖上第二列的狀態。

因此,本題的狀態可以這樣表示:

dp[i][state]表示該填充第i列,第i-1列對它的影響是state的時候的方法數。i<=M,0<=state<2N

對於每一列,情況數也有很多,但由於N很小,所以可以採取搜尋的辦法去處理。對於每一列,搜尋所有可能的放木塊的情況,並記錄它對下一列的影響,之後更新狀態。狀態轉移方程如下:

dp[i][state]=∑dp[i-1][pre]每一個pre可以通過填放成為state

程式碼

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#define exp 1e-9
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define mod 1000000007
using namespace std;
typedef long long LL;
int n,m;
LL dp[1005][35];
//1000列32個狀態

//第i列,列舉到了第j行,當前狀態是state,對下一列的影響是nex
void dfs(int i,int j,int state,int nex)
{
    if(j==n)
    {
        dp[i+1][nex]+=dp[i][state];
        dp[i+1][nex]%=mod;
        return;
    }
    //如果這個位置已經被上一列所佔用,直接跳過
    if( ( (1<<j)&state) > 0)
        dfs(i,j+1,state,nex);
        
        //如果這個位置是空的,嘗試放一個1*2的
    if( ( (1<<j)&state) == 0)
        dfs(i,j+1,state,nex|(1<<j));
        
        //如果這個位置以及下一個位置都是空的,嘗試放一個2*1的
    if (j+1<n && ((1<<j)&state)==0 && ((1<<(j+1))&state)==0)
        dfs(i,j+2,state,nex);
        
    return;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(dp,0,sizeof(dp));
        if(n==0 && m==0) break;
        dp[1][0]=1;
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<(1<<n);j++)
            {
                if(dp[i][j])
                    dfs(i,0,j,0);
            }
        }
        printf("%lld\n",dp[m+1][0]);
    }
    return 0;
}