hiho_1048_状态压缩2

题目大意

用1x2的单元拼接出 NxM的矩形,单元可以横放或者纵放,N < 1000, M <= 5. 求不同的拼接方案总数。

分析

计算机解决问题的基本思路:搜索状态空间。如果采用dfs进行搜索,则可以将当前搜索的位置(i, j)作为状态,而不保存棋盘的占用情况,每次扩展时进行判断是否可行。这样可以得到最后的结果,但当然会超时。 
    (1) 参考hiho_1048中的提示,可以知道在搜索的时候按照固定的顺序可以大幅度剪枝(通过将无序的搜索变成有序的搜索来实现)。然后可以对中间结果进行保存,即记忆化搜索。状态dp[i][j][s1][s2] 表示求第i行,第j列位置开始扩展,且第i行的占用状态为s1, 第i+1行的占用状态为s2 时,填满剩余的空格的方案总数。

(2)将记忆化搜索直接使用动态规划来实现,状态 dp2[i][k1][k2] 表示前i-1行都已经填满了,此时第i行的占用情况的二进制表示转换为十进制为 k1,第i+1行的占用情况的二进制表示转换为十进制为 k2,达到此时棋盘占用情况的摆放方案总数。 
    这样,可以进行状态转移时,枚举第i行的占用情况k1,和第i+1行的占用情况k2,看是否可以进行扩展,blabla...

实现

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
#define BIT(i, j) (i & (1 << (j - 1)))
const int mod = 1000000007;
int dp[1005][5][35][35];

/*记忆化搜索
n,m为边界n行,m列。
求第i行,第j列位置开始扩展,且第i行的占用状态为s1, 第i+1行的占用状态为s2 时,填满剩余的空格的方案总数
*/
int dfs(int n, int m, int i, int j, int s1, int s2){
    if (dp[i][j][s1][s2] != -1)
        return dp[i][j][s1][s2];
    int result = 0;
    //边界!! 无法继续扩展时,取值为1,表示所有扩展到此的合理情况的初始均为1
    if (i > n || j > m || s1 >= (1 << m) || s2 >= (1 << m))
        return 1;
    //如果第i行位置j(从右向左)被占用,且j < m,向左扩展
    if (BIT(s1, j) && j < m)
        result = dfs(n, m, i, j + 1, s1, s2);
    //如果第i行位置j(从右向左)被占用,且j = m,向下一行扩展
    else if (BIT(s1, j) && j == m)
        result = dfs(n, m, i + 1, 1, s2, 0);
    //如果第i行位置j没有被占用,且无法向左或者向下扩展
    else if (BIT(s1, j) == 0 && (j == m || BIT(s1, j + 1)) && (i == n || BIT(s2, j)))
        result = 0;
    //如果第i行位置j没有被占用,且只能向左扩展
    else if (BIT(s1, j) == 0 && j < m && BIT(s1, j + 1) == 0 && (i == n || BIT(s2, j)))
        result = dfs(n, m, i, j, s1 | (1 << (j - 1)) | (1 << j), s2);
    //如果第i行位置j没有被占用,且只能向下扩展
    else if (BIT(s1, j) == 0 && (j == m || BIT(s1, j + 1)) && (i < n && BIT(s2, j) == 0))
        result = dfs(n, m, i, j, s1 | (1 << (j - 1)), s2 | (1 << (j - 1)));
    //如果第i行位置j没有被占用,且可以向左或向下扩展
    else if (BIT(s1, j) == 0 && j < m && BIT(s1, j + 1) == 0 && i < n && (BIT(s2, j) == 0))
        result = dfs(n, m, i, j, s1 | (1 << (j - 1)) | (1 << j), s2) +
        dfs(n, m, i, j, s1 | (1 << (j - 1)), s2 | (1 << (j - 1)));
    //记忆化保存
    return dp[i][j][s1][s2] = result % mod;
}

//动归数组, dp2[i][k1][k2] 表示前i-1行都已经填满了,此时第i行的占用情况的二进制表示转换为十进制为 k1,
// 第i+1行的占用情况的二进制表示转换为十进制为 k2
int dp2[1005][35][35];
int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    /*
    记忆化搜索
    memset(dp, -1, sizeof(dp));
    int result = dfs(n, m, 1, 1, 0, 0);
    printf("%d\n", result);
    */

    //动态规划
    memset(dp2, 0, sizeof(dp2));
    //初始状态,第1行未被占用,只有1种可能。
    dp2[1][0][0] = 1;
    int mx = 1 << m;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            //枚举第i行的所有情况(占用情况的二进制表示转换为十进制)
            for (int k1 = 0; k1 < mx; k1++){
                //如果第i行的第j个位置(从右向左)为0,则可以从这里开始扩展
                if ((k1 & (1 << (j - 1))) == 0){
                    //看是否可以横向扩展
                    if (j < m && ( ((1 << j) & k1) == 0)){
                        //状态转移
                        for (int k2 = 0; k2 < mx; k2++){
                            dp2[i][k1 | (1 << j) | (1 << (j - 1))][k2] =
                                (dp2[i][k1 | (1 << j) | (1 << (j - 1))][k2] + dp2[i][k1][k2]) % mod;
                        }
                    }
                    if (i < n) //看是否可以纵向扩展
                        //当前的第i+1行的占用情况
                        for (int k2 = 0; k2 < mx; k2++){
                            //如果第i+1行的第j个位置(从右向左)为0,则可以纵向扩展
                            if (((1 << (j-1)) & k2) == 0)
                                //状态转移
                                dp2[i][k1 | (1 << (j - 1))][k2 | (1 << (j - 1))] =
                                (dp2[i][k1 | (1 << (j - 1))][k2 | (1 << (j - 1))] + dp2[i][k1][k2]) % mod;
                        }
                }
            }
        }
        //当第i行全部被占用满时,可以将第i+1行的状态 转移到 i+1行的当前行
        for (int k = 0; k < mx; k++){
            dp2[i + 1][k][0] = dp2[i][mx - 1][k];
        }
    }
    //最后的结果是n行全部占满,此时第n行为 mx-1, 第n+1行为0
    printf("%d\n", dp2[n][mx-1][0]);
    return 0;
}
时间: 2024-12-30 00:14:12

hiho_1048_状态压缩2的相关文章

胜利大逃亡(续)(状态压缩bfs)

胜利大逃亡(续) Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 7357    Accepted Submission(s): 2552 Problem Description Ignatius再次被魔王抓走了(搞不懂他咋这么讨魔王喜欢)……这次魔王汲取了上次的教训,把Ignatius关在一个n*m的地牢里,并在地牢的某些地方安装了带

uva 818(dfs+图+状态压缩)

题意:有n个环,编号从1到n,给出了一些环环相扣的情况,比如给a和b表示a和b两个环的扣在一起的,每个环都是可以打开的,问最少打开多少个环,然后再扣好,可以让所有的环成为一条链. 题解:状态压缩把所有的打开环的情况枚举出来,然后拿去判断是否成立,更新打开环后的图g[i][j],和每个点的度数,不成立有三种情况,1.计算没有打开的环的度数,如果大于2说明不会有链,2.把没有打开环拿去dfs,访问过就vis[i]++,如果vis[i]>=2说明存在环,3.如果打开的环数num + 1小于链的数量,说

POJ 3254 Corn Fields 状态压缩DP (C++/Java)

http://poj.org/problem?id=3254 题目大意: 一个农民有n行m列的地方,每个格子用1代表可以种草地,而0不可以.放牛只能在有草地的,但是相邻的草地不能同时放牛, 问总共有多少种方法. 思路: 状态压缩的DP. 可以用二进制数字来表示放牧情况并判断该状态是否满足条件. 这题的限制条件有两个: 1.草地限制. 2.相邻限制. 对于草地限制,因为输入的时候1是可以种草地的. 以"11110"草地分析,就只有最后一个是不可以种草的.取反后得00001  .(为啥取反

uva 11195 Another queen (用状态压缩解决N后问题)

题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2136 Problem A Another n-Queen Problem I guess the n-queen problem is known by every person who has studied backtracking. In this problem you s

dp状态压缩

dp状态压缩 动态规划本来就很抽象,状态的设定和状态的转移都不好把握,而状态压缩的动态规划解决的就是那种状态很多,不容易用一般的方法表示的动态规划问题,这个就更加的难于把握了.难点在于以下几个方面:状态怎么压缩?压缩后怎么表示?怎么转移?是否具有最优子结构?是否满足后效性?涉及到一些位运算的操作,虽然比较抽象,但本质还是动态规划.找准动态规划几个方面的问题,深刻理解动态规划的原理,开动脑筋思考问题.这才是掌握动态规划的关键. 动态规划最关键的要处理的问题就是位运算的操作,容易出错,状态的设计也直

HDU3001(KB2-J 状态压缩dp)

Travelling Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 8103    Accepted Submission(s): 2642 Problem Description After coding so many days,Mr Acmer wants to have a good rest.So travelling is

2017盛大游戏杯 零件组装(状态压缩DP之巧妙枚举子集)

题目链接:2017盛大游戏杯 零件组装 题意: 有n个零件,给你相邻关系和排斥关系,每两块零件组装起来有一个代价,问最少的代价总和是多少. 题解: 考虑状态压缩,dp[i]表示i这个集合为一个零件块. 那么要枚举一下i的子集.O(3^n). 先要预处理一下每个集合的排斥个数和相邻个数,然后容斥一下就可以了. 1 #include<bits/stdc++.h> 2 #define mst(a,b) memset(a,b,sizeof(a)) 3 #define F(i,a,b) for(int

HDU1565(状态压缩dp)

方格取数(1) Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 8170    Accepted Submission(s): 3095 Problem Description 给你一个n*n的格子的棋盘,每个格子里面有一个非负数.从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数

POJ 1753 Flip game状态压缩+广搜

题意有4*4的16个方格,每个方格有黑白两种颜色,每次点击方格后,被点击方格本身及其上下左右的方格都会改变颜色.给出一组状态,求将这组状态变为全白或者全黑至少需要点击几次.若无法达到,则输出Impossible. 样例输入bwwbbbwbbwwbbwww 样例输出4 思路每个方格只有黑白两种颜色,且只有16个方格,因此可以把每个状态看作一个16位的二进制数(状态压缩),2^16=25536,因此可以用int来保存状态.然后就是BFS暴搜,直到状态为0或者65535(全1)为止. 注意点65535