问题描述
农夫有一块m ∗ * ∗n的等分网格田地,他想在田地上种草。要求种草的格子田必须互不相连,现给出每块格子田各自的种植性质(能种(1)/不能种(0)),求种草方案总数。(1=<n,m<=12)
问题分析
- 问题的规模很小,这让人想到状态压缩。
- 我们分别为每一排的情况做一个状态压缩,原因如下:
- 对于单独的一块土地来说,只有种与不种两种选择。
- 状态压缩一般直接用于一维情况。
- 状态压缩总是需要枚举所有状态的:使用一维压缩,需要位数最多为12,则总状态数最多为 2 12 = 4 k 2^{12}=4k 212=4k;如果使用二维压缩,需要位数最多为 12 ∗ 12 = 144 12*12=144 12∗12=144,总状态数最多为 2 144 2^{144} 2144,过于巨大,显然不可取。
- 对于任何一排的某种种草方案,方案可行仅当
- 方案本身可行,即不存在行上的连续种草
- 方案与土地性质匹配,即不可种草的土地不种草
- 方案与上下行协调,即不存在列上的连续种草
- 为有效实现以上思想,定义数据如下:
- F[maxn],F[i]表示第i行的田地性质(二进制状态压缩)
- f[maxn][1<<maxn],f[i][j]表示在第i行种草方案为j的情况下 利用了前i行(包括第i行)田地的种草方案数
- ok[1<<maxn],a[i]表示单行方案i的单行合法性(有无连续种草)
- 在实现时,涉及到的位操作如下:
- F[i]=(F[i]<<1)+a[i][j] 将第i行的数字转成二进制位,如 101 → \to → 5,110 → \to → 6
- (j&F[i])==j 判断第i行的种草方案j与土地性质F[i]是否匹配,如j=101,F[i]=111,则(j&F[i])=101;j=101,F[i]=011,则(j&F[i])=001;
- !(j&k) 判断第j行和第k行是否存在列连续种草,如j=101,k=010,则j&k=000;j=101,k=110,则j&k=100;
- 动态规划状态转移式
f [ i ] [ j ] = ∑ k f [ i − 1 ] [ k ] f[i][j]=\sum_{k}f[i-1][k] f[i][j]=k∑f[i−1][k]
式中,j为第i行的种草方案,k为第i-1行的种草方案,j与k必须相互协调。 - 关于取模,采取边算边取,这样可仍然选用int:$(a+b+c)\ %\ p== ((a+b)\ %\ p+c)\ %\ p $
AC代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const bool debug=false;//用于控制debug测试信息的输出
const int maxn=12,M=1e8;
int n,m,ans=0;
int a[maxn][maxn];
int F[maxn],f[maxn][1<<maxn];//F[i]表示第i行的田地性质(二进制状态压缩),f[i][j]表示在第i行种草方案为j的情况下 利用了前i行(包括第i行)田地的种草方案数
bool ok[1<<maxn];//a[i]表示单行状态i的合法性(有无连续种草)
int main()
{
//输入数据
scanf("%d%d",&m,&n);
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
scanf("%d",&a[i][j]);
//对每一排的田地性质进行二进制状态压缩
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
F[i]=(F[i]<<1)+a[i][j];
//预判段每种种草方案的合法性,初始化第0行方案数
for(int i=0;i<(1<<n);i++){
if(!(i&(i<<1)) && !(i&(i>>1))){
ok[i]=true;
if((i&F[0])==i)f[0][i]=1;
}
if(debug){
printf("[%d]=%d ",i,ok[i]);
if(i==(1<<n)-1){
printf("\n");
//for(int i=0;i<(1<<n);i++)
// printf("f[0][%d]=%d ",i,f[0][i]);
printf("\n");
}
}
}
//动态规划
for(int i=1;i<m;i++){
for(int j=0;j<(1<<n);j++){
if(ok[j] && (j&F[i])==j){
for(int k=0;k<(1<<n);k++){
if(ok[k] && (k&F[i-1])==k && !(j&k))
f[i][j]=(f[i][j]+f[i-1][k])%M;
}
}
}
}
if(debug){
for(int i=0;i<m;i++){
for(int j=0;j<(1<<n);j++)
printf("f[%d][%d]=%d ",i,j,f[i][j]);
printf("\n");
}
}
//统计信息,输出结果
for(int j=0;j<(1<<n);j++)
ans=(ans+f[m-1][j])%M;
printf("%d\n",ans);
return 0;
}
参考博客
- https://www.luogu.org/blog/Owencodeisking/solution-p1879 (本题洛谷题解)
- https://www.cnblogs.com/Tony-Double-Sky/p/9283254.html (状态压缩知识探究)