洛谷题目链接
题目的大致意思就是在给定的n * m
的矩形方阵内种草,有一些方块是不能种的,且种草需要满足一个条件,就是不能相邻的草方块,(上下左右),问给定的n * m
的矩阵种草的方案数。(1 ≤ n,m≤ 12)
这道题标准解法是使用状压dp,状压是状态压缩的简称,意思是将一个复杂的不好表示的状态通过算法压缩成为一个简单的,好表示的状态,便于运算和转移。通常情况是使用位运算来进行压缩和计算。
在这道题中,我们可以将一行的种草情况看作是一个状态,那么这个状态就由m个方块组成,有 2m种可能的情况,遍历起来非常的麻烦。一个朴素的压缩思想是使用哈希算法,将m个位置的种草情况用01来表示,1是种草了,0则是没有。这样,一行的状态就可以用一个m位的二进制数字
来表示。
例如,下面的情况,绿色格子代表艹,白色则是没有种草的地方:
这个状态就可以表示为:010110101,对应的十进制数字为:181。
而前面提到的2m种情况,在该压缩算法下其实就是m位从全0到全1的过程,此时枚举这些情况只需要循环逐个遍历即可。
遍历所有的情况:
for(int i = 0;i < 1 << m;i++)//i < 1 << m 就表明i的上限就是m个1
表示好了状态,后面就需要给出在转移状态时需要处理的相关问题的解决方案了。处理好这些问题,就可以开开心心的转移了。
想要判断某个状态是否存在左右相邻的情况,一个非常朴素的做法就是将数字一位一位的拆开,看看是否有相邻的1即可:
bool check(int k){
int num = 0; //上一位的状态
for(int i = 0;i < m;i++){
if(num & k & 1){
return false; //false表示这个装填不合法
}
num = k & 1;
k >>= 1;
}
return true; //该状态合法
}
这个方法的复杂度是O(m),但是考虑到m的范围,其实复杂度就是一个常数。但是我们总是希望有更优的解决方案,好在这道题确实有这样的方案。
考虑到检验是否存在两个1相邻,我们可以通过位运算的方式O(1)进行判定。具体的思路就是将原数左移或者右移一位,新的数字再与原数相与,如果存在相邻的1,那么结果一定不为零(1 & 1 = 1) 。这时我们的check
函数就可以大大简化了:
bool check(int k){
return k & (k << 1); //该写法与上面方法结果逻辑相反,非零是true代表不合法,反之亦然
}
当我们枚举了本行的状态j
和上面一行的状态k
,如何判断他们是否存在上下相邻的情况呢?
其实在解决上一个问题时就已经说出了该问题的解决方法,那就是使用与运算,如果存在上下相邻的情况,运算的结果不为零。所以判断合法的方式就是:k & j
非零为不合法,零为合法。
如何判断一个状态是否将艹都种到了合法的方块上了呢?其实做法还是使用与运算。
我们需要先将每行的地形表示成为一个二进制数。由于题目中允许种艹的地形是1,因此在计算的时候需要先取一下反。:
//mp[i]表示第i行的地形
for(int i = 0;i < n;i++){
for(int j = 0,x;j < m;j++){
mp[i] <<= 1;
cin >> x;
mp[i] |= !x;
}
}
在枚举状态j时,需要先检验一下是否在该行地形下合法:
if(j & mp[i]) continue;//非零不合法
解决完了问题,下面就可以开始转移了:
我们使用数组dp[i][j]
来表示第i
行状态j
的方案数
对于第一行,我们需要初始化,合法的状态初值为1否则为0:
for(int i = 0;i < 1 << m;i++){
if(i & (i << 1) || i & mp[0])
continue;
dp[0][i] = 1;
}
对于第i行(i ≠1),枚举所有的在该行合法状态j
。对于每一个j,枚举上一行所有合法且不与j形成上下相邻的状态k
。
转移方程为:dp[i][j] = dp[i][j] + dp[i - 1][k]
for(int i = 1;i < n;i++){
for(int j = 0;j < 1 << m;j++){
if(j & (j << 1) || j & mp[i])
continue;
for(int k = 0;k < 1 << m;k++){
if(k & (k << 1) || k & mp[i - 1] || k & j)
continue;
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mo; //mo取模数
}
}
}
最终答案就是最后一行各状态的方案数总和。
long long ans = 0;
for(int i = 0;i < 1 << m;i++){
ans = (ans + dp[n - 1][i]) % mo;
}
cout << ans;
#include
using namespace std;
int mp[20];
long long dp[20][1 << 13];
long long mo = 100000000;
int m,n;
int main(){
cin >> n >> m;
for(int i = 0;i < n;i++){
for(int j = 0,x;j < m;j++){
mp[i] <<= 1;
cin >> x;
mp[i] |= !x;
}
}
for(int i = 0;i < 1 << m;i++){
if(i & (i << 1) || i & mp[0])
continue;
dp[0][i] = 1;
}
for(int i = 1;i < n;i++){
for(int j = 0;j < 1 << m;j++){
if(j & (j << 1) || j & mp[i])
continue;
for(int k = 0;k < 1 << m;k++){
if(k & (k << 1) || k & mp[i - 1] || k & j)
continue;
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mo;
}
}
}
long long ans = 0;
for(int i = 0;i < 1 << m;i++){
ans = (ans + dp[n - 1][i]) % mo;
}
cout << ans;
}
import java.util.Scanner;
public class Main {
private static Scanner scan;
public static void main(String[] args) {
scan = new Scanner(System.in);
int n = scan.nextInt();
int m = scan.nextInt();
long mo = 100000000;
int[] mp = new int[n];
long[][] dp = new long[n][1 << m];
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
mp[i] <<= 1;
mp[i] |= (1 - scan.nextInt());
}
}
for(int i = 0;i < 1 << m;i++){
if((i & (i << 1)) != 0 || (i & mp[0]) != 0)
continue;
dp[0][i] = 1;
}
for(int i = 1;i < n;i++){
for(int j = 0;j < 1 << m;j++){
if((j & (j << 1)) != 0 || (j & mp[i]) != 0)
continue;
for(int k = 0;k < 1 << m;k++){
if((k & (k << 1)) != 0 || (k & mp[i - 1]) != 0 || (k & j) != 0)
continue;
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mo;
}
}
}
long ans = 0;
for(int i = 0;i < 1 << m;i++){
ans = (ans + dp[n - 1][i]) % mo;
}
System.out.print(ans);
}
}