POJ2411(状压Dp,插头Dp,数学公式)
2017-04-12 17:12
330 查看
题目大意:有一个由n*m组成的地砖,现在拿1*2的地砖来铺,要求必须铺满。求总方案数(n,m<=11)。
这是一道状压Dp和插头Dp的经典题目。不过,或许很多人和我一样,看到这种题目后,都会想到搜索。可是搜索的时间复杂度是O(2^nm),肯定超时了。
【解法一:状压Dp】
首先设f[i][j]为前i行且第i行状态为j时的方案总数。那么f[i][j] = sum(f[i-1][j'] | j'能到达j);反过来,对于f[i][j],它可以向f[i+1][j'']贡献(其中j能到达j‘’)。
那样就可以设铺的为1,不铺的为0。拿1*2的地砖来铺,无非就是横铺或者竖铺。显然:如果第i行的第k位不铺,又由于必须铺满,所以第i+1行的第k为必须铺。也就是说,如果把状态j取反,二进制中为1的那一个位置就必须竖着铺。
所以,对于每一行的状态j,可以用DFS来算出j能到达的状态,则这个算法的时间复杂度为O(n*2^2m)。
小优化:若n和m都是奇数,则答案为0。
证明:因为 n,m为奇数,
不妨设:n=2q-1,m=2p-1(q,p为正整数),
所以:nm=(2q-1)(2p-1) = 4qp - 2q - 2p + 1
又因为:4qp,2q,2p为偶数,
所以:nm为奇数。
因为是拿1*2的地砖铺,且必须铺满。
所以nm是偶数,矛盾,正毕。
附上代码:
【解法二:插头Dp】:
设f[i][j][k]表示当前枚举到第i行第j列的地砖,且前面m个地砖(不包括(i,j))的状态为k(k用二进制表示,1表示有,0表示没有)。则有三种情况。
(1):不放,当且仅当k的第m位为1,才可以转移状态;
(2):往左放,当k的第m位为1,k的第1位为0且j不在最左时,可转移;
(3):往上放,当k的第m位为1且i不在最上面的一行时,转移;
我们可以根据上面的特点,用二进制表示出来。
因为每一个(i,j)只和上一次有关,则可以用滚动数组优化空间。
时间复杂度O(nm*2^m)。
程序:
数学公式可以做:即
,但我们知道c++的三角函数和开方有硕大的浮点误差,或许产生精度问题。
这是一道状压Dp和插头Dp的经典题目。不过,或许很多人和我一样,看到这种题目后,都会想到搜索。可是搜索的时间复杂度是O(2^nm),肯定超时了。
【解法一:状压Dp】
首先设f[i][j]为前i行且第i行状态为j时的方案总数。那么f[i][j] = sum(f[i-1][j'] | j'能到达j);反过来,对于f[i][j],它可以向f[i+1][j'']贡献(其中j能到达j‘’)。
那样就可以设铺的为1,不铺的为0。拿1*2的地砖来铺,无非就是横铺或者竖铺。显然:如果第i行的第k位不铺,又由于必须铺满,所以第i+1行的第k为必须铺。也就是说,如果把状态j取反,二进制中为1的那一个位置就必须竖着铺。
所以,对于每一行的状态j,可以用DFS来算出j能到达的状态,则这个算法的时间复杂度为O(n*2^2m)。
小优化:若n和m都是奇数,则答案为0。
证明:因为 n,m为奇数,
不妨设:n=2q-1,m=2p-1(q,p为正整数),
所以:nm=(2q-1)(2p-1) = 4qp - 2q - 2p + 1
又因为:4qp,2q,2p为偶数,
所以:nm为奇数。
因为是拿1*2的地砖铺,且必须铺满。
所以nm是偶数,矛盾,正毕。
附上代码:
#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> using namespace std; #define Maxn 14 int sta[1<<Maxn]; long long f[Maxn][1<<Maxn]; int n,m,fp,p; void dfs(int num,int p) { if(p+1==m) { sta[fp++] = num; return; } dfs(num,p+1);//不铺 if((num&(1<<p)) || (num&(1<<(p+1)))) return; dfs(num|(3<<p),p+1);//横着铺 } int main() { while(scanf("%d%d",&n,&m) && n && m) { memset(f,0,sizeof(f)); if(n*m%2)//小优化 { printf("0\n"); continue; } fp=0; dfs(0,0);//预处理第一行 for(int i=0;i<fp;i++) f[1][sta[i]] = 1; p = 1<<m; for(int i=1;i<n;i++) for(int j=0;j<p;j++)//枚举当前状态j { fp = 0; dfs(p-1-j,0); for(int k=0;k<fp;k++)//枚举j能到达的状态 f[i+1][sta[k]] += f[i][j]; } cout<<f [p-1]<<endl; } return 0; }
【解法二:插头Dp】:
设f[i][j][k]表示当前枚举到第i行第j列的地砖,且前面m个地砖(不包括(i,j))的状态为k(k用二进制表示,1表示有,0表示没有)。则有三种情况。
(1):不放,当且仅当k的第m位为1,才可以转移状态;
(2):往左放,当k的第m位为1,k的第1位为0且j不在最左时,可转移;
(3):往上放,当k的第m位为1且i不在最上面的一行时,转移;
我们可以根据上面的特点,用二进制表示出来。
因为每一个(i,j)只和上一次有关,则可以用滚动数组优化空间。
时间复杂度O(nm*2^m)。
程序:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; #define Maxn 14 long long f[2][1<<Maxn];//注意long long int n,m,p,cur; void change(int x,int y)//状态转移 { if(y & (1<<m)) f[cur][y^(1<<m)] += f[cur^1][x]; } int main() { while(scanf("%d%d",&n,&m) && n && m) { if(n*m%2) { printf("0\n"); continue; } if(n<m) swap(n,m);//小优化 memset(f,0,sizeof(f)); int p = 1<<m; f[cur=0][p-1] = 1; for(int i=0;i<n;i++) for(int j=0;j<m;j++) { cur^=1; memset(f[cur],0,sizeof(f[cur])); for(int k=0;k<p;k++) { change(k,k<<1);//不放 (第k位一定为1) if(i && !(k&1<<m-1)) change(k,(k<<1) ^ (1<<m) ^ 1);
//往上放 (注意到“1<<m”,满足第k位一定为0,才是合法的,因为change函数进行转移时必须满足"y&(1<<m)",然后y|(1<<m)可以去掉它) if(j && !(k&1)) change(k,(k<<1)^3);//往左放 } } printf("%lld\n",f[cur][p-1]); } return 0; }
数学公式可以做:即
,但我们知道c++的三角函数和开方有硕大的浮点误差,或许产生精度问题。
相关文章推荐
- CCF 201312-4 有趣的数 (数位DP, 状压DP, 组合数学+暴力枚举, 推公式, 矩阵快速幂)
- 插头dp-基于连通性,维护轮廓线的状压dp
- POJ 2411 Mondriaan's Dream ——状压DP 插头DP
- poj2411_Mondriaan's Dream_状压DP(插头)
- [bzoj 1879] [Sdoi2009]Bill的挑战:状压DP,自创数学公式(?)
- [Poj2411]Mondriaan's Dream(状压dp)(插头dp)
- hdu4652(概率dp->数学公式)
- SGU495Kids and Prizes(数学期望||概率DP||公式)
- 【POJ2411】Mondriaan's Dream-状态压缩DP(插头DP?)
- Lucene打分公式的数学推导
- Objective-C中math.h数学计算公式介绍
- Markdown 数学公式
- UVA 11270 Tiling Dominoes [插头dp]
- UVA - 580 Critical Mass(dp 数学)
- [Tex学习笔记]尝试数学公式
- 博客中添加数学公式
- 数学公式~~持续更新
- 【BZOJ1426】收集邮票 概率DP 论文题 推公式题
- 【poj2411】Mondriaan's Dream 状态压缩dp
- 求和(数学公式推导、取余运算)