题目地址:https://www.luogu.com.cn/problem/P1879
题目内容:农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
题目解析:同样是状态压缩,和 状态压缩入门 P2622 关灯问题II 不同之处在于,维度由1维变为2维。问题考虑当前行,当前行的理论方案数目为2^n种。用dp[i][j]来表示第i行选用方案j的可行方案数目(dp[i][j]由dp[i-1]行的可行方案转变过来。)
dp[i][j]的值需要先进行规则判断:
1、行内判断:①方案j是否满足没有相邻的草地(连续的11),判断方式 (!(j&(j>>1)))&(!(j&(j<<1))),其实就是将j和j左移一位和右移一位进行比较,结果必须为0;②是否在贫瘠土地种草,用一个数组g[i] 代表第i行的输入状态,(j&g[i])==j,其实就是将j中贫瘠的土地设为0,然后和原来的j比较,判断得出是否在贫瘠土地上种草。
2、与上一行比较:k为i-1行的某个方案,如果(k&j)==0,则上一行的k状态可以转移到本行的j状态,dp[i][j] = (dp[i][j]+dp[i-1][k])%mod; 加上上一行的方案
除此之外,需要注意的地方是初始状态可以设置一个dp[0][0]=1,这样可以给第一行一个方案初值,很有效,含义是初始行不种草(题目认为这也是一种方案),方案数为1。
#include<iostream> using namespace std; // checkRow: 不考虑贫瘠土地的理论情况下,行内可行方案 // orgin: 土地原状 // dp:递归数组,dp[i][j]存储了自上至下到达[i,j]位置可行方案数 const int mod = 1e8; int m,n,checkRow[1<<15],orgin[20],dp[20][1<<15]; int main(){ cin>>m>>n; int temp; // 用于记录读入的每个数字 for(int i=1;i<=m;i++){ // 读入每一行的原始状态 for(int j=1;j<=n;j++){ cin>>temp; orgin[i] = (orgin[i]<<1) + temp; //依次向前左移动一位 } } dp[0][0] = 1; // 全都不种 // 不考虑贫瘠土地的行列理论可行方案 for(int i=0;i<(1<<n);i++) checkRow[i] = (!(i&(i>>1)))&&(!(i&(i<<1))); // 与左移一位和右移一位比较 for(int i=1;i<=m;i++){ for(int j=0;j<(1<<n);j++){ // 首先要行内合适 if(checkRow[j]&&((j&orgin[i])==j)){ // 理论可行,且没有占用贫瘠土地 // 把当前状态和上一行的所有状态进行比较 for(int k=0;k<(1<<n);k++){ if((k&j)==0) dp[i][j] = (dp[i][j]+dp[i-1][k])%mod; // 上一行的k方案可转移到当前行的j方案 } } } } int res = 0; // 累加最后一个行可行方案即可 for(int i=0;i<(1<<n);i++) res = (res+dp[m][i])%mod; cout<<res<<endl; return 0; }
状压DP2 P1879 [USACO06NOV]Corn Fields G
原文:https://www.cnblogs.com/zhuanghuaijilie/p/12804419.html