POJ 2411 Mondriaan's Dream (状压dp)

来源:互联网 发布:淘宝卖家怎么改差评 编辑:程序博客网 时间:2024/06/06 00:56

题目原文:

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

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.

解题思路:问题具备一定的递推性质所以采用动态规划算法。

                如何制定状态,仅考虑一个格子的话,状态分析分析较为复杂。结合状态压缩dp常见的套路,将一列的格子作为一个整体,用一个正数的二进制位来表明一列格子的状态,1表示该格子被覆盖,0表示该格子尚未被覆盖。

状态: dp[i][j] -> 第i列状态为j时的填充方法

状态转移:列与列进行转移,如果两列的状态放在一起仍是合法的话,可以将前一行的 j 状态转移到后一行的 k 状态

                 dp[i&1][k] += dp[1-(i&1)][j];

这里关键的一步在于如何判断两个状态放在一起是否合法,这里采用dfs的方法,判断出可以与给定状态相邻的状态并记录。

       具体的细节参考代码。

注:因为数据量过大采用滚动数组


AC代码:

/*    @Author: wchhlbt    @Date:   2017/2/20*///#include <bits/stdc++.h>#include <vector>#include <list>#include <map>#include <set>#include <queue>#include <stack>#include <bitset>#include <algorithm>#include <functional>#include <numeric>#include <utility>#include <sstream>#include <iostream>#include <iomanip>#include <cstdio>#include <cmath>#include <cstdlib>#include <ctime>#include <cstring>#include <limits>#include <climits>#include <cstdio>#define Fori(x) for(int i=0;i<x;i++)#define Forj(x) for(int j=0;j<x;j++)#define maxn 2100#define inf 0x3f3f3f3f#define ONES(x) __builtin_popcount(x)using namespace std;typedef long long ll ;const double eps =1e-8;const int mod = 1000000007;typedef pair<int, int> P;const double PI = acos(-1.0);int dx[4] = {0,0,1,-1};int dy[4] = {1,-1,0,0};int n,m;bool vis[maxn][maxn];//记录状态是否可以转移的矩阵ll dp[2][maxn];//使用滚动数组 dp[i][j] -> 第i列状态为j时有多少种方法ll ans[15][15];void dfs(int x, int i, int z){//x 当前状态  i当前行数   z预期状态    //通过递归判断两个状态是否可以转移并记录    if(i>n) {   vis[x][z]=true; return; }    if(x & (1<<(i-1)))    {        dfs(x,i+1,z);        if(i<n && (x&(1<<i)))            dfs(x,i+2,z+(1<<(i-1))+(1<<i));    }    else        dfs(x,i+1,z+(1<<(i-1)));}int main(){    //freopen("test.txt","r",stdin);    ios_base::sync_with_stdio(false);    cin.tie(0);        while(cin>>n>>m && n+m){        if((n*m) & 1){  cout << 0 << endl;  continue; }        if(ans[n][m]){  cout << ans[n][m] << endl;  continue; }                if(m<n) swap(m,n);//让n尽可能的小,提高效率        ll up = (1<<n);        memset(dp,0,sizeof(dp));        memset(vis,0,sizeof(vis));        for(int i = 0; i<up; i++)            dfs(i,1,0);        for(int i = 0; i<up; i++)//记录初始状态,将前一列都放满            dp[1][i] = vis[up-1][i];        for(int i = 2; i<=m; i++){            memset(dp[i&1],0,sizeof(dp[i&1]));//及时清空本列数组            for(int j = 0; j<up; j++){                for(int k = 0; k<up; k++){                    if(vis[j][k])//如果可以从状态 j 转移到状态 k                        dp[i&1][k] += dp[1-(i&1)][j];                }            }               }        cout << dp[m&1][up-1] << endl;//最终状态是第m列全部放满的情况 所以 j = (1<<n)-1        ans[n][m] = ans[m][n] = dp[m&1][up-1];//记忆化处理    }    return 0;}


0 0
原创粉丝点击