AcWing 291 蒙德里安的梦想 题解 (动态规划—DP—状态压缩DP)

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>

using namespace std;

const int N = 15, M = 1 << N;

long long f[N][M];
bool st[M];
int n, m;
vector<int> S[M];
 
int main()
{
	while(cin>>n>>m, n || m){
		for(int i = 0; i < 1 << n; i ++ ){//这里的i表示的是任何一列在这种01排列情况下是合法的(1表示从这一列的这个位置有横向摆放的砖,可能是上一列伸进来的,也可能是这一列伸到下一列的
		 	bool v = true; 
		 	int res = 0;//res 记录这种情况下一列连续的0的个数 
			for(int j = 0; j < n; j ++ ){//j表示行数,从这一列从上往下遍历 
			 	if(i >> j & 1){//如果这一列的第j个为砖头,判断之前有几个连续的不是砖头的位置 
				 	if(res & 1){//如果连续不是砖头的位置数是奇数,代表这种情况不合法,标记false 
					 	v = false;
					 	break;
					} 
					res = 0;//如果这种情况合法,就让res = 0,继续向下遍历 
				}
				else res ++ ;//如果这一列第j个位置不是砖头,就让res ++ ; 
			} 
			if(res & 1) v = false;//判断最后一段没有砖头的位置的数量的奇偶性
			st[i] = v;//记录这种状态是否合法 
		}
		//筛选出所有情况是否合法之后,就开始将合法的情况加入S中
		for(int i = 0; i < 1 << n; i ++ ){//表示第i列的所有状态 
			S[i].clear(); 
			for(int j = 0; j < 1 << n; j ++ ){//表示第i - 1列的所有状态 
				if((i & j) == 0 && st[i | j]){
					//i表示第i列的状态,j表示第i - 1列的状态,当两列状态不冲突时(即i & j == 0),表示这两列相邻,例如:i = 10010, j = 01101,这时表示这两列砖头的位置排列不冲突
					//st[]记录的是所有合法情况,i的情况表示第i- 2列伸到第i - 1列的砖头排列情况,j表示第i - 1列伸到第i列的砖头排列情况,i | j表示第i - 1列的砖头排列情况,如果这种排列情况合法
					//例如:i = 100001, j = 010010,i | j = 110011,这就是一种合法情况 
					S[i].push_back(j);//将i - 1列加入S中,记录这一列可以这么排列 
				}
			}
		} 
		memset(f, 0, sizeof(f));
		memset(st, 0, sizeof(st));
		f[0][0] = 1;//初始化,表示第0列伸向第1列的砖头数为0的合法数情况是1 
		for(int i = 1; i <= m; i ++ ){//i表示列数 
			for(int j = 0 ; j < 1 << n; j ++ ){//遍历这一列所有合法的状态 
				for(auto k : S[j]){//如果j状态可行,就状态转移 
					f[i][j] += f[i - 1][k];//当前列的合法方案数就等于i - 1列合法方案数的累加 
				}
			} 
		} 
		cout<<f[m][0]<<endl;//m表示第m + 1列,f[m][0]表示第m+1列都是0的状态,即第m列没有伸向m+1列的砖头 
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值