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\n", next[0]); } return 0; }