洛谷P1879玉米田(状压DP)
题目描述
农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
输入输出格式
输入格式:
第一行:两个整数M和N,用空格隔开。
第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。
输出格式:
一个整数,即牧场分配总方案数除以100,000,000的余数。
输入输出样例
输入样例#1:
2 3
1 1 1
0 1 0
输出样例#1:
9
\(Solution\):
这题需要较强的位运算基础,比如像我这种位运算渣渣就搞了好久的位运算才来做这题
首先,我们需要一个数组 \(g [ i ]\) 来记录每一行的状态, 即每一行有哪些位置可以种草
for(int i=1;i<=n;i++)//数组a[i][j]表示是否可以种草
for(int j=1;j<=m;j++)
{
a[i][j]=read();
g[i]=(g[i]<<1)+a[i][j];
//g[i]的递推,二进制中1表示能种,0表示不能种
}
对于每一行,最多有 \(2^m-1\) 种状态,但是其中有些状态就已经有两块相邻的地种了草,比如:11010010 。所以我们还需要排除每一行的所有状态中有相邻1的情况,即:
for(int i=0;i<mx;i++) v[i]=((i&(i<<1))==0)&&((i&(i>>1))==0);
这里的v[i]是bool数组,至于 \((\ (\ i\ \&\ (\ i<<1)\ )==0\ )\ \&\&\ (\ (\ i\ \&\ (\ i > > 1\ )\ ) = = 0\ )\) 本人也是搞了好久才弄懂qwq
\(( i<<1)\) 表示把 i 左移了了一位,然后与上 i ,如果 i 里面有两个连续的1,那么 ( i & ( i < < 1 ) ) 的值就不会是0。例:i = 101100 ,所以 \(( i < < 1 ) = 1011000\),然后:
i | 0 | 1 | 0 | 1 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|
i<<1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 |
与运算 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
同理,当 i = 101100 时,( i > > 1 ) = 10110,它们做与运算:
i | 1 | 0 | 1 | 1 | 0 | 0 |
---|---|---|---|---|---|---|
i>>1 | 0 | 1 | 0 | 1 | 1 | 0 |
与运算 | 0 | 0 | 0 | 1 | 0 | 0 |
具体实现请见代码:
#include<bits/stdc++.h>
#define il inline
using namespace std;
const int md=1e8;
il int read(){
int f=1,w=0;char c=0;
while(!isdigit(c))
{
if(c=='-') f=-1;
c=getchar();
}
while(isdigit(c)) w=w*10+(c^48),c=getchar();
return f*w;
}
int n,m,a[15][15],g[15],mx,f[15][4100],ans;
bool v[4100];
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
a[i][j]=read();
g[i]=(g[i]<<1)+a[i][j];
}//递推求g[i]
mx=1<<m;//mx-1=状态数最大值
f[0][0]=1;//初始值,第0行一块草也不种
for(int i=0;i<mx;i++)
v[i]=((i&(i<<1))==0)&&((i&(i>>1))==0);
//排除每一行的所有状态中有相邻1的情况
for(int i=1;i<=n;i++)//枚举行
for(int j=0;j<mx;j++)//枚举这一行的状态
if(v[j]&&(j&g[i])==j)
//(j&g[i]):在g[i]中,1表示能种草;在j中,1表示要种草
for(int k=0;k<mx;k++)
if((k&j)==0)
//若上一行与这一行的某一列都是1,则与运算值不为0
f[i][j]=(f[i][j]+f[i-1][k])%md;
for(int i=0;i<mx;i++)
ans=(ans+f[n][i])%md;
cout<<ans<<endl;
return 0;
}