POJ2411 Mondriaan's Dream 轮廓线dp

第一道轮廓线dp,因为不会轮廓线dp我们在南京区域赛的时候没有拿到银,可见知识点的欠缺是我薄弱的环节。

题目就是要你用1*2的多米诺骨排填充一个大小n*m(n,m<=11)的棋盘,问填满它有多少不同的方法。 一个可行的解法就是轮廓线dp。 假设我们从上往下,从左往右去填,那么我们会发现,假如我们当前填的是(i,j)格的时候,在它前面的(i',j')其实是已经确定一定填了的,所以实际上没有填的时候处于轮廓线的部分,在这里没有具体

1 1 1 1
1 1 x x
x x    
       

如上图,当我们填红色的x那一格的时候,前面的(i',j')必然为1,而实际上我们要考虑的是x 的那一部分,这一部分就是所谓的轮廓线,x的取值可能是不确定的,所以我们可以用位压缩表示出xxxx的状态。转移的时候根据的就是三种情况,1是红色的x 已经填了,这个时候不用放,相应的转移情况,还有就是红色的x没填,它可以打横放以及打竖放。

因为用到位压缩,所以要求的是棋盘一定具有窄边框的特性,就是有一维上必须是10左右,由于每一格是一个阶段,每次转移2^m种状态,每种状态转移的复杂度是O(1),所以总体的复杂度是O(n*m*2^m)。

还有一些变形是棋盘上本身就已经有一些障碍,这个只需要在转移的时候多加注意即可,我后面也会做一道试试。

#pragma warning(disable:4996)
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstdio>
#define ll long long
using namespace std;

int n, m;
ll dp[2][1 << 12]; // 二维滚动数组

int main()
{
	while (cin >> n >> m &&(n||m))
	{
		memset(dp, 0, sizeof(dp));
		ll *cur, *next;
		cur = dp[0]; next = dp[1];
		cur[0] = 1;
		for (int i = 0; i < n; i++){
			for (int j = 0; j < m; j++){
				memset(dp[(i*m+j+1)&1], 0, sizeof(dp[(i*m+j+1)&1]));
				cur = dp[(i*m + j) & 1]; next = dp[(i*m + j + 1) & 1];
				for (int k = 0; k < 1 << m; k++){
					// 如果已经放了,那就直接转移
					if ((k >> j) & 1){
						next[k & ~(1 << j)] += cur[k];
					}
					else{
						// 尝试横放
						if (j + 1 < m && !(k >> (j + 1) & 1)){
							next[k | 1 << (j + 1)] += cur[k];
						}
						// 尝试竖放
						if (i + 1 < n){
							next[k | 1 << j] += cur[k];
						}
					}
				}
			}
		}
		printf("%lld
", next[0]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/chanme/p/3561258.html