POJ 2411 Mondriaan's Dream (状压dp)

题目原文:

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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值