poj 2411 Mondriaan's Dream (状态dp)
2017-04-23 11:13
369 查看
题目
这个题目的题意很容易理解,在一个N*M的格子里,我们现在有两种类型的 砖块,1 * 2 和 2 * 1,问一共有多少种方案,可以将整个N*M的空间都填满。
最简单的例子就是下面的了:
题解:
状态标记 横放和竖放的下一个均为1,竖放的上一个和不放置为0 ,每行可以转化为1个2进制数。为什么要这样呢,应为这样表示肯定是包括了pre(前一行)和now(后一行)的所有状态的(而且多了很多,后面的主要工作是怎样删掉这些多的),而且可以区分,还容易区分。也就是说从pre转到now状态时要做的事不是很多。
或者还有一种理解方法:pre为1表示now与pre无关,为0就表示有关;也就是:
1. 在横着贴砖的时候,(i, j), (i, j+1) 都是1,这个值其实对下一行如何选择没有影响。
2. 竖着贴砖的第二个,我们也选择了1, 因为这个砖头结束了,对下一行如何选择依然没有影响。
3. 而竖着的第一个砖头,这个砖头是对下面有影响的,如果(i,j)是0,那么(i+1, j)只有是1的情况下才能满足条件。
那好下面介绍3种方法(越来越叼):
一:暴力:就是三重循环,如果pre的状态k与now的状态j匹配的话dp[i][j] += dp[i-1I][k];
然后懒得写了,具体想知道就看代码和注释吧:
好,那现在介绍第二种方法:
上面是先枚举pre和now的状态,再看他们是否匹配;
你会发现很多状态都是不匹配的;
现在是先枚举now的状态,在去搜索可用的pre状态;或者先枚举pre再搜now也行;
代码:
哦,令外说一点就是求第一行的所有可用状态,可以假设有0行,dp[1][j] = dp[0][1<<m-1],相当于第一行是第零行全为1的状态发展而来(第一行与第0行无关,自由发挥),但是比直接求会慢一点;
方法三:
你们发现一个东西没,每次从pre转到now时都是一样的东西;比如第一行的i状态可以转到第二行的j;那么第二行的i肯定也可以转到第3行的j;
这样的话,就可以先把所有的状态都先存下来,然后直接取出来用。
代码:
这个题目的题意很容易理解,在一个N*M的格子里,我们现在有两种类型的 砖块,1 * 2 和 2 * 1,问一共有多少种方案,可以将整个N*M的空间都填满。
最简单的例子就是下面的了:
题解:
状态标记 横放和竖放的下一个均为1,竖放的上一个和不放置为0 ,每行可以转化为1个2进制数。为什么要这样呢,应为这样表示肯定是包括了pre(前一行)和now(后一行)的所有状态的(而且多了很多,后面的主要工作是怎样删掉这些多的),而且可以区分,还容易区分。也就是说从pre转到now状态时要做的事不是很多。
或者还有一种理解方法:pre为1表示now与pre无关,为0就表示有关;也就是:
1. 在横着贴砖的时候,(i, j), (i, j+1) 都是1,这个值其实对下一行如何选择没有影响。
2. 竖着贴砖的第二个,我们也选择了1, 因为这个砖头结束了,对下一行如何选择依然没有影响。
3. 而竖着的第一个砖头,这个砖头是对下面有影响的,如果(i,j)是0,那么(i+1, j)只有是1的情况下才能满足条件。
那好下面介绍3种方法(越来越叼):
一:暴力:就是三重循环,如果pre的状态k与now的状态j匹配的话dp[i][j] += dp[i-1I][k];
然后懒得写了,具体想知道就看代码和注释吧:
//这个代码是直接复制别人的; #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <queue> #include <algorithm> #include <map> #include <cmath> #include <iomanip> #define INF 99999999 typedef long long LL; using namespace std; const int MAX=(1<<11)+10; int n,m; LL temp[MAX],dp[MAX],bin[15]; bool mark[MAX]; bool check(int i){ while(i){ if(i&1){ i>>=1; if(!(i&1))return false;//第j列是1则第j+1列必须是1 i>>=1;//继续判断下一列 }else i>>=1;//继续判断下一列 } return true; } void Init(){ memset(mark,false,sizeof mark); memset(temp,0,sizeof temp); for(int i=0;i<bin[m];++i){//初始化第一行和可以到达什么状态 if(check(i))temp[i]=1,mark[i]=true; } } void DP(){ for(int k=2;k<=n;++k){ for(int i=0;i<bin[m];++i)dp[i]=0; for(int i=0;i<bin[m];++i){ for(int j=0;j<bin[m];++j){ if((i|j) != bin[m]-1)continue;//每一位或之后必须每一位是1(综合前面3种情况和分析可知) if(!mark[i&j])continue;//由初始化和前面分析三种情况分析可知i&j必须得到和初始化可以到达的状态一样才行 dp[i]+=temp[j];//i可以从j到达,则增加j的方案数 } } for(int i=0;i<bin[m];++i)temp[i]=dp[i];/*这个滚动有点瓜皮^.^; } /* 那他上面那两个判断就很有灵性了;可能很多人还没理解;我在举例子来说明一下。 首先我们那样定义就是有两个地方要解决,第一个是你不能在第i-1行放一个竖的(放0)又在第i行放一个竖的; 也就是双零情况,所以只要满足i|j是满的(每位都为一)就行了; 第二种就是不能放半个横的;又懒得讲了,自己举个例子就好了; */ } int main(){ bin[0]=1; for(int i=1;i<12;++i)bin[i]=2*bin[i-1]; while(~scanf("%d%d",&n,&m),n+m){ if(n<m)swap(n,m);//始终保持m<n,提高效率,少了会超时的,这是个大优化; Init(); DP(); printf("%lld\n",temp[bin[m]-1]);//输出最后一行到达时的状态必须全部是1 } return 0; }
好,那现在介绍第二种方法:
上面是先枚举pre和now的状态,再看他们是否匹配;
你会发现很多状态都是不匹配的;
现在是先枚举now的状态,在去搜索可用的pre状态;或者先枚举pre再搜now也行;
代码:
哦,令外说一点就是求第一行的所有可用状态,可以假设有0行,dp[1][j] = dp[0][1<<m-1],相当于第一行是第零行全为1的状态发展而来(第一行与第0行无关,自由发挥),但是比直接求会慢一点;
//这就是我自己写的; #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <map> using namespace std; const int MAXN = 16; int sPath, m, v; long long dp[2][1<<11]; void dfs(int k, int pre, int now) {//dfs自行体会 if (k == m) { dp[v^1][now] += dp[v][pre]; return; } if (now>>k&1) { dfs(k+1, pre, now); if (k+1 < m && now>>(k+1)&1) dfs(k+2, pre|1<<k|1<<(k+1), now); } b7dd else dfs(k+1, pre|1<<k, now); } int main() { int n; //freopen("in.txt", "r", stdin); while(scanf("%d%d", &n ,&m)) { if (!n && !m) break; if (n&1 && m&1) { printf("0\n"); continue; } if (n < m) { n = n^m; m = n^m; n = n^m; } v = 0; memset(dp[0], 0, sizeof(dp[0])); dp[0][(1<<m)-1] = 1;//这就是我注意说的, for(int i = 1; i <= n; i++) { memset(dp[v^1], 0, sizeof(dp[v^1])); //if(i == 1) j = (1<<m)-1; //可以这样优化一下,就差不多了; for(int j = 0; j < 1<<m; j++) dfs(0, 0, j); v ^= 1;//这样滚才有灵性 } printf("%I64d\n", dp[v][(1<<m)-1]); } }
方法三:
你们发现一个东西没,每次从pre转到now时都是一样的东西;比如第一行的i状态可以转到第二行的j;那么第二行的i肯定也可以转到第3行的j;
这样的话,就可以先把所有的状态都先存下来,然后直接取出来用。
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <map> using namespace std; const int MAXN = 16; int path[1<<16][2], sPath, m; //path小了会蜜汁re哦; long long dp[2][(1<<11)]; void dfs(int k, int pre, int now) { if (k == m) { path[sPath][0] = pre; path[sPath++][1] = now; return ; } dfs(k+1, pre<<1|1, now<<1); dfs(k+1, pre<<1, now<<1|1); if (k+2 <= m) dfs(k+2, pre<<2|3, now<<2|3); } int main() { int n, v; while(scanf("%d%d", &n ,&m)) { if (!n && !m) break; if (n&1 && m&1) { printf("0\n"); continue; } if (n < m) { n = n^m; m = n^m; n = n^m; } sPath = 0; dfs(0, 0, 0); v = 0; memset(dp[0], 0, sizeof(dp[0])); dp[0][(1<<m)-1] = (long long)1; for(int i = 1; i <= n; i++) { memset(dp[v^1], 0, sizeof(dp[v^1])); for(int j = 0; j < sPath; j++) dp[v^1][path[j][1]] += dp[v][path[j][0]]; v ^= 1; } printf("%I64d\n", dp[v][(1<<m)-1]); } }
相关文章推荐
- poj 2411 Mondriaan's Dream(状态压缩+dp)
- Chapter06-Mondriaan's Dream(POJ 2411)(状态压缩DP)
- poj 2411 Mondriaan's Dream 状态压缩dp
- poj 2411 Mondriaan's Dream_状态压缩dp
- HDOJ 1400 & POJ 2411 - Mondriaan's Dream 状态压缩DP
- poj 2411 Mondriaan's Dream(状态压缩dp)
- 【poj 2411】Mondriaan's Dream 状态压缩DP
- POJ2411 - Mondriaan's Dream(状态压缩DP)
- poj 2411 && zoj 1100 Mondriaan's Dream ———状态压缩dp
- poj 2411 Mondriaan's Dream (状态压缩dp 入门)
- poj 2411 Mondriaan's Dream(状态压缩dp)
- poj 2411 Mondriaan's Dream 状态压缩DP
- POJ-2411 Mondriaan's Dream 状态压缩DP
- poj 2411 Mondriaan's Dream(状态压缩DP)
- POJ 2411 Mondriaan's Dream (状态压缩dp)
- POJ 2411 Mondriaan's Dream(DP---状态压缩)
- POJ 2411 Mondriaan's Dream 状态压缩DP
- poj 2411/hdu 1400 Mondriaan's Dream 状态压缩dp
- POJ 2411 Mondriaan's Dream(状态DP)
- poj-2411 Mondriaan's Dream (状态压缩dp)