代码仿照自:https://fanfansann.blog.youkuaiyun.com/article/details/108298578
用二进制代表每一行的状态,如100001
其中:
0代表小方块横着放或者这一行和上一行有竖着放的小方块
1代表这一行和下一行有竖着放的小方块
遍历每一行,第i行只与第i-1行有关,第i - 1行的状态k能转移到第i行的状态j,当且仅当
1、 j & k == 0, 即第i列和第i - 1列的每一行都没有连续两个横向棋子重叠放置
2、 flag[j | k] == true 即第i列和第i - 1列合并以后(要合并是因为第i列和第i - 1列都是两个空格子才行,一列有竖棋子的上半部分一列没有是不算的)的状态没有出现连续空格子为奇数个
public class Solution {
//0代表小方块横着放或者这一行和上一行有竖着放的小方块
//1代表这一行和下一行有竖着放的小方块
public static long StateCompressionDP(int high,int length)
{
//保证length不大于high
if(length>high)return StateCompressionDP(length,high);
//如果面积是奇数的话那就没救了
if(((high*length)&1)==1)return 0;
//一行一行来,每行填满之后才能填下一行,一行有length列
//每列有0或者1两种状态,所以一共有1<<length种状态//dp[i][j]代表
//有dp[i][j]种路径使第i行变成第j种状态
long[][]dp=new long[high][1<<length];
// flag[i]=true代表第1行的第i种状态可行
boolean[] flag=new boolean[1<<length];
//首先初始化第0行的状态
for(int i=0;i<flag.length;i++)
{
flag[i]=true;
//count计算0的个数,因为只需要知道是奇数还是偶数所以可以一直加
int count=0;
//k是第i种状态的二进制表现形式
int k=i;
for(int j=0;j<length;j++)
{
//遇到了1说明连续的0被截断了
if((k&1)==1)
{
//此时连续0的个数是奇数个
if((count&1)==1) {
flag[i] = false;
break;
}
}
//遇到了0
else
{
count++;
}
k>>=1;
}
//如果此时0的个数是奇数个也不合法
if((count&1)==1)flag[i]=false;
//第0行可行的就令dp[0][i]为1
dp[0][i]=flag[i]==true?1:0;
}
//i遍历的是每一行,i从1开始是因为第0行初始化好了
for(int i=1;i<dp.length;i++)
//j遍历的是第i行的所有状态
for(int j=0;j<dp[0].length;j++)
//k遍历的是第i-1行的所有状态
for(int k=0;k<dp[0].length;k++)
{
// 若第i-1行的状态x和第i行的状态y满足flag[x|y]==false则代表不可行,因为第一行只
//判断了连续的0是否是偶数个,而flag[x|y]==false代表了i-1与i行连续n列都是0的n为奇数
//两行的某一列都是1是不允许的,详情看定义
if((j&k)==0&&flag[j|k])
{
dp[i][j] += dp[i - 1][k];
}
}
//为什么只返回dp[high-1][0]呢?因为最后一行必须全0,如果有1意味着需要下一行
return dp[high-1][0];
}
public static void main(String[] args) {
int length=5;
int high=8;
System.out.println(StateCompressionDP(length, high));
}
}