题意:给m*n的场地,1代表可选,0代表不可选,选择的场地不能有边相邻(即不能上下左右四个方向),求选择的方案数,一个也不选也是一种选择方法。
思路:dp[i][j]代表考虑了前i行,第i行状态为j的方案数。枚举本行的可行状态,枚举上一行的状态
注意:位运算不加括号很容易出错!
注意与P1896 互不侵犯对比
区别1:P1896要求一定要放置完所有的国王,由于不知道在哪一行用完k个国王,所以最终结果是dp[i][j][k](遍历i,j,k是国王总数)之和,本题可以任选个数,因此没有第三维的限制,结果是考虑完所有行之后各个状态下的累加值。
区别2:P1896中每一行的可行状态(不考虑别的行的影响)都是相同的,因此可以先预处理出来,减少循环次数。本题中每一行的可行状态是不一样的,学到了 将每行值存为十进制,判断状态j在第i行是否可行 (a[i]&j)==j 则可行
加深了对这两题的理解,dp[i][j]还是考虑了前i行,在考虑第i行的时候将前i-1行的所有可转移状态都考虑过了,不可转移的也考虑过了(为0)
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=15;
int a[N]; //每一行表示的十进制数
int dp[N][1<<N]; //考虑前i行,第i行状态为j的方案数
const int mod=1e8;
int m,n;
int ans;
bool judge(int i,int j) //判断j状态在第i行是否可行
{
if((a[i]&j)!=j) return 0; //如果a[i]二进制为1的位包含了J的,与值为j
if((j&(j<<1))||(j&(j>>1))) return 0; //水平方向相邻
return 1;
}
void solve()
{
int tot=(1<<n)-1;
memset(dp,0,sizeof(dp));
for(int i=0;i<=tot;++i)
{
if(judge(1,i)) dp[1][i]=1; //这个就包含了状态为0 x&0==0 dp[1][0]=1
}
for(int i=2;i<=m;++i)
{
for(int j=0;j<=tot;++j)
{
if(judge(i,j))
{
for(int p=0;p<=tot;++p)
{
if(j&p) continue;
dp[i][j]=(dp[i][j]+dp[i-1][p])%mod;
}
}
}
}
for(int i=0;i<=tot;++i)
ans=(ans+dp[m][i])%mod;
cout<<ans<<endl;
}
int main()
{
cin>>m>>n;
for(int i=1;i<=m;++i)
{
a[i]=0;
for(int j=1;j<=n;++j)
{
int tmp;
cin>>tmp;
a[i]=(a[i]<<1)+tmp; //我去,不加括号的话好像会先算1+tmp。。。
}
}
solve();
return 0;
}