Java蒙德里安动态图_AcWing 291. 蒙德里安的梦想(Java版)

本题解是参考 算法竞赛进阶指南 视频所写,推荐去看这里面的视频题解。

所有的要点,都在注释中体现了

/*

核心:先放横着的,再放竖着的

总方案数:等于只放横着的小方块的合法方案数

如何判断,当前方案是否合法? 所有剩余位置,能否填充满竖着的小方块。

可以按列来看,每一列内部所有连续空着的小方块的数量需要是偶数个。(因为要放竖着的2x1的小方格)

状态表示:化0为整

f(i,j):表示前i-1列已经摆好,且从i-1列,伸出的小方格在第i列的状态是j的方案集合

状态转移:化整为0

考虑从i-2、i-1、i列。由于i-1到i列的划分已经固定,所以划分的依据是从i-2列伸出到i-1列的状态k来划分

条件:

1、由于要没有交集,所以: k & j == 0

2、由于i-1列已经固定,所以其连续空着的小方格数量必须要为偶数。就是i|j:中每一段连续的0是否是偶数

*/

import java.util.*;

public class Main {

static int N = 12, M = 1 << N;

static int n, m;

// f[i][j]:表示前i-1列已经摆好,且从i-1列,伸出的小方格在第i列的状态是j的方案的总数量

static long[][] f = new long[N][M];

// 这就是表示每一个数的二进制数,每一段连续的0是否是偶数

static boolean[] st = new boolean[M];

public static void main(String[] args) {

Scanner sc = new Scanner(System.in);

while(true) {

n = sc.nextInt(); m = sc.nextInt();

if(n == 0 && m == 0) break;

// 预处理这一列所表示的二进制数中,连续的0是否是偶数个。这里是满足条件2

for(int i = 0; i < 1 << n; i ++) {

int cnt = 0;

boolean is_valid = true;

// 查看这个二进制数中,每一段连续的0的个数是否是偶数

for(int j = 0; j < n; j ++) {

// 如果相与为1,就需要判断这一段0的个数

if(((i >> j) & 1) == 1) {

// 判断当前一段的0是否是偶数个

if((cnt & 1) == 1) {

is_valid = false;

break;

}

// 走到这里,就说明这一段的个数为偶数,这里需要将cnt清零,来计算下一段

cnt = 0;

}else cnt ++;

}

// 这里是看最后一段连续的0是否是偶数个。相与为1就代表为这一段是奇数个0

if((cnt & 1) == 1) is_valid = false;

st[i] = is_valid;

}

// 由于是多组数据,所以每一次都需要进行重置

for (int i = 0; i < N; i++) Arrays.fill(f[i], 0);

// 由于不存在-1列,所以从-1列伸出到第0列状态为0(就相当于没有伸出),

// 是一种合法方案,所以初始化为1。

// 其他状态 > 0,由于不存在-1列,所以其他都为0

f[0][0] = 1;

// 枚举时:需要考虑 i-2列、i-1列、i列

// 从i-2伸出到i-1列的状态是 k,从i-1列伸出到i列的状态是 j

// 枚举每一列,从第2列开始,因为没有-1列存在。

for(int i = 1; i <= m; i ++) {

// 这是枚举第i列

for(int j = 0; j < 1 << n; j ++) {

// 这是枚举第i-1列所有状态,也就是从i-2列伸出到i-1列

for(int k = 0; k < 1 << n; k ++) {

// 满足两个条件

if((j & k) == 0 && st[j | k]) {

f[i][j] += f[i - 1][k];

}

}

}

}

// 代表从下标m-1(也就是第m列)伸出的小方格的状态为0。

// 也就是第m列不横放小方格伸出到m+1列,就是我们最终求出的方案数

System.out.println(f[m][0]);

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值