【状态压缩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;
}