【状压dp】棋盘覆盖


 【状态压缩dp】棋盘覆盖


Accept: 0    Submit: 0
Time Limit: 1 second    Memory Limit : 131072 KB

 Problem Description

有一个N*M(1<=N<=5,1<=M<=1000)的棋盘,现在有1*2和2*1的小木块无数个,

要想盖满整个棋盘,会有多少种可行的方法?
      答案如果大于1,000,000,007,mod1,000,000,007即可。

 Input

有一行,第一行是棋盘的宽度和长度。

 Output

可行的方法数。

 Problem Idea

解题思路:

【题意】

 这道题假设第一列已经填满,则第二列的摆设方式只与第一列对它的影响有关。
  同理,第三列的摆设方式也只与第二列对它的影响有关。
   那么就可以使用一个长度为N的二进制数state来表示这个影响,例如:4(00100) 
  因此,本题的状态可以这样表示:
  dp[i][state]表示填充第i列,第i-1列对它的影响是state的时候的方法数。其中i<=M,0<=state<2N 
  换句话说,dp[i][j]用于存储填充i-1列状态为j时,i列可能出现的方法数。
 对于每一列,情况数也有很多,但由于N很小,所以可以采取搜索的办法去处理。
 对于每一列,搜索所有可能放木块的情况,并记录它对下一列的影响,
 之后更新状态。
 状态转移方程如下:
 dp[i][state]=∑dp[i-1][pre]
  每一个pre可以通过填放成为下一列的state状态

【类型】
状态压缩dp

【分析】

对于每一列的深度优先搜索,写法如下:

//第i列,枚举到了第j行,当前状态是state,对下一列的影响是nex
void dfs(int i, int j, int state, int next){//i代表列数,j代表当前位数(也可以说是行数-1,初始时为0)
                                         //state代表状态数,nex代表下一列出现的状态
    if(j==N){
        dp[i+1][next]+=dp[i][state];
        return;
      }
    if(((1<<j)&state)>0){//如果这个位置已经被占用,跳过
        dfs(i, j + 1, state, next);
    }
    if(((1<<j)&state)==0){//如果这个位置是空的,尝试放一个左右覆盖1*2的木块
        dfs(i, j + 1, state, next | (1 << j));
    }
    if(j+1<N &&((1<<j)&state)==0&&((1<<j+1)&state)==0){//如果这个位置和下一个位置空的,尝试放一个上下覆盖2*1的木块,而此时要跳过下一个木块
        dfs(i, j + 2, state, next);
        //注意j+1<N
    }
    //return;

}
状态转移的方式如下:
  for(int i=1;i<=M;i++){//外层遍历每一列
            for(int j=0;j<(1<<N);j++){//内层遍历每一行的各种状态
               if(dp[i][j]){   //
                   dfs(i, 0, j, 0);//如果方法数不空,就执行dfs
               }
            }
        }

【时间复杂度&&优化】

 O(n²)


 Source Code

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int N,M;//棋盘的宽和长
long long dp[1005][34];dp[i][j]用于存储填充i-1列状态为j时,i列可能出现的方法数

void dfs(int i, int j, int state, int next){//i代表列数,j代表当前位数(也可以说是行数-1,初始时为0)
                                         //state代表状态数,nex代表下一列出现的状态
    if(j==N){
        dp[i+1][next]+=dp[i][state];
        return;
      }
    if(((1<<j)&state)>0){//如果这个位置已经被占用,跳过
        dfs(i, j + 1, state, next);
    }
    if(((1<<j)&state)==0){//如果这个位置是空的,尝试放一个左右覆盖1*2的木块
        dfs(i, j + 1, state, next | (1 << j));
    }
    if(j+1<N &&((1<<j)&state)==0&&((1<<j+1)&state)==0){//如果这个位置和下一个位置空的,尝试放一个上下覆盖2*1的木块,而此时要跳过下一个木块
        dfs(i, j + 2, state, next);
        //注意j+1<N
    }
    //return;

}

int main() {
    while(cin>>N>>M){
        memset(dp,0,sizeof(dp));
        dp[1][0]=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);//如果方法数不空,就执行dfs
               }
            }
        }
        cout<<dp[M+1][0]<<endl;
    }
    return 0;
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值