poj2411 棋盘覆盖

Mondriaan's Dream
Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 12395   Accepted: 7233

Description

Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series' (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways. 

Expert as he was in this material, he saw at a glance that he'll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won't turn into a nightmare!

Input

The input contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.

Output

poj2411 棋盘覆盖_第1张图片For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.

Sample Input

1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0

Sample Output

1
0
1
2
3
5
144
51205

经典的棋盘覆盖问题。横放看做(11)竖放看做(01),然后按行状压dp。

未优化题解:题解 

优化题解:题解 

优化是用了dfs去找出第i-1行状态s与第i行哪些状态scur相匹配,而并不是盲目枚举所有状态。这题要理解dfs是有些困难的,再发一篇题解:题解 ,这份题解里的dfs和上面优化代码是类似的,dfs里都考虑了3种情况,

情况一:第i-1行第j列为0,那么第i行第j列只能为1

情况二:第i-1行第j列为1,第i行第j列为0

那么第三种情况有人可能会认为是:第i-1行第j列为1,第i行第j列为1,显然并不是这么简单,比如这种情况

0 1

1 1

这个最后一列1是和1搭配,但是前一列显然对应了竖着放,而最后一列不可能是竖着放,只可能是最后一行横着放,那么就矛盾了,因此如果简单的<第i-1行第j列为1,第i行第j列为1>,dfs往下就会出错,因此可以发现,第三种情况应为:第i-1行第j-1,j列都为1,第i行第j-1,j列都为1。至于第二种情况,你可能怀疑会不会也出现类似第三种情况这种矛盾呢?但是枚举状态后是不会发生的,因此也就是这三种情况了。

优化代码基本还是在状压dp,dfs是对一个特定的状态s进行匹配,而我发的最后一份题解,则换了个思路,是用dfs将所有可能的两行状态全部枚举出来,然后就相当于有了一幅状态转移图,并且对第0行置为全1,这样做的好处是不干扰第一行的放置,因为只有前一行有0,才会影响后一行,这种做法真是太巧妙了。至于后者的做法,令人震惊,他可以解决第一种方法无法解答问题,也就是poj3420.

这个见我的另外一篇博客,传送门:快速幂。

优化的状压dp代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#define ll long long
using namespace std;

ll dp[1<<11][11];
int n,m;
bool check(int x){ //检查第一行合法状态
    while(x){
        if((x&1)&&(x&2)) x>>=2; //出现11
        else if(!(x&1))x>>=1; //出现0
        else return false; //出现01
    }
    return true;
}
void dfs(int r,int c,int s,int scur){ //搜索r-1行状态s可以r行匹配的所有状态scur
    if(c>m) return;
    if(c==m) {dp[scur][r]+=dp[s][r-1];return;}
    if(s>>c&1){
        dfs(r,c+1,s,scur); //1->0
        if(s>>c&2)
            dfs(r,c+2,s,scur|3<<c); //11->11
    }
    else dfs(r,c+1,s,scur|1<<c); //0->1
}
int main()
{
    while(cin>>n>>m,n){
        if(n*m&1) {puts("0");continue;}
        if(m>n) swap(n,m);
        memset(dp,0,sizeof dp);
        for(int s=0;s<1<<m;s++) //初始化第一行
            if(check(s)) dp[s][0]=1;
        for(int i=1;i<n;i++)
            for(int s=0;s<1<<m;s++) //枚举i-1行状态
                dfs(i,0,s,0);
        cout<<dp[(1<<m)-1][n-1]<<endl;
    }
	return 0;
}

状态转移思想:

#include<cstdio>
#include<iostream>
#include<cstring>
#define ll long long
#define Maxn 2048
using namespace std;

int chg[Maxn*Maxn][2];
ll dp[1<<13][13];
int n,m,tot;
void dfs(int c,int s,int scur){
    if(c>m) return;
    if(c==m){
        chg[tot][0]=s;
        chg[tot++][1]=scur;
        return;
    }
    dfs(c+1,s<<1,scur<<1|1); //0->1
    dfs(c+1,s<<1|1,scur<<1); //1->0
    dfs(c+2,s<<2|3,scur<<2|3); //11->11
}
int main()
{
    while(cin>>n>>m,n){
        if(m>n) swap(n,m);
        tot=0;
        dfs(0,0,0);
        memset(dp,0,sizeof dp);
        dp[(1<<m)-1][0]=1;
        for(int i=1;i<=n;i++){
            for(int j=0;j<tot;j++)
                dp[chg[j][1]][i]+=dp[chg[j][0]][i-1];
        }
        cout<<dp[(1<<m)-1][n]<<endl;
    }
    return 0;
}


你可能感兴趣的:(poj2411 棋盘覆盖)