Corn Fields(状压dp模板题)

传送门 poj 3254

题目描述

Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can’t be planted. Canny FJ knows that the cows dislike eating close to each other, so when choosing which squares to plant, he avoids choosing squares that are adjacent; no two chosen squares share an edge. He has not yet made the final choice as to which squares to plant.

Being a very open-minded man, Farmer John wants to consider all possible options for how to choose the squares for planting. He is so open-minded that he considers choosing no squares as a valid option! Please help Farmer John determine the number of ways he can choose the squares to plant.

输入

Line 1: Two space-separated integers: M and N
Lines 2..M+1: Line i+1 describes row i of the pasture with N space-separated integers indicating whether a square is fertile (1 for fertile, 0 for infertile)

输出

Line 1: One integer: the number of ways that FJ can choose the squares modulo 100,000,000.

样例

  • Input
    2 3
    1 1 1
    0 1 0
  • Output
    9

题解

  • 友情翻译一波:农夫有一块地,被划分为m行n列大小相等的格子,其中一些格子是可以种植的(用1标记),农夫可以在这些格子里种上玉米,其他格子则不能种植(用0标记),并且要求不可以使相邻格子都玉米。现在输入数据给出这块地的大小及可否种植的情况,求该农夫有多少种种植方案可以选择(注意:任何格子都不放也是一种选择,不要忘记考虑!
  • 思路:
    样例第一行状态数:
编号 状压(压缩前) 状态(压缩后)
1 0 0 0 0
2 0 0 1 1
3 0 1 0 2
4 1 0 0 4
5 1 0 1 5
  • 我们用1表示在这块地上种上了玉米,用0表示没有种植玉米,这样我们就可以用0和1表示每一行的所有状态,也就是每一种状态可以用一个二进制数表示。当我们需要对状态之间进行处理,即判断是否有两块地相邻时,我们判断是否有两个1相邻即可。但这种方法未免太过复杂,并且时间也不允许,所以我们对于每一种状态,用一个十进制数替换掉它所对应的二进制数。
  • 当我们要判断一行的某种状态a是否有两个玉米相邻的时候,我们只需要将这个十进制数a左移一位,然后对原来的数进行&运算( a & (a<<1) ),若得到的结果不为0,则有两个玉米相邻。
  • 当我们要判断当前行的某种状态a是否与上一行的某种状态b有冲突(即列相邻)时,只需要判断 a & b是否为零即可。
  • 我们用dp[i][j]表示第i行第j种可行状态与前面有多少种组合方式,状态转移方程为:dp[i][j]=dp[i-1][k1]+dp[i-1][k2]+…+dp[i-1][kt]。kt为上一行可行方案的编号,共有t种(前提是状态之间不会冲突)。
    若第i行有num种可行方案,则从1到i行的总的可行方案就有dp[i][1]+dp[i][2]+…+dp[i][num]种。那么从1到m行的可行方案总和就是dp[m][1]+dp[m][2]+…+dp[m][num]种。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
#define INIT(a,b) memset(a,b,sizeof(a))
#define LL long long int
const int MAX=0x7fffffff;
const int Min=-0x7fffffff;
const int Mod=100000000;
int state[5000],cant[20]; //所有可行状态和不能放的状态
int Num; //可行状态数
int M,N; //M行N列
int dp[20][1000]; //dp[i][j]:第i行第j种状态的情况数
void FindState(){ //排除掉有相邻1的状态
Num=0;
int all=1<<N;
for(int i=0;i<all;i++) //不能包括all,因为all比要求多一位
if((i&(i<<1))==0) //(i&(i<<1)) ==优先级比&高
state[++Num]=i;
}
inline bool Judge(int x,int i){ //排除包含禁止种植区域的状态
if(x&cant[i])return false;
else return true;
}
int main(){
while(cin>>M>>N){
FindState();INIT(cant,0);INIT(dp,0);
int tem;
for(int i=1;i<=M;i++){
for(int j=1;j<=N;j++){ //储存每一行不能放的状态
cin>>tem;
if(tem==0)
cant[i]+=(1<<(N-j));
}
}

for(int i=1;i<=Num;i++) //处理第一行
if(Judge(state[i],1))
dp[1][i]=1;

for(int i=2;i<=M;i++){ //枚举每一行
for(int j=1;j<=Num;j++){ //枚举第i行的所有状态
if(!Judge(state[j],i))continue; //若这些状态里面跟cant[i]有重合部分则continue
for(int k=1;k<=Num;k++){ //枚举第i-1行的所有状态
if(!Judge(state[k],i-1))continue; //若这些状态里面跟cant[i-1]有重合部分则continue
if(state[k]&state[j])continue; //若有上下相邻 则continue
dp[i][j]=(dp[i][j]+dp[i-1][k])%Mod;
}
}
}

int ans=0;
for(int i=1;i<=Num;i++)
ans=(ans+dp[M][i])%Mod;
cout<<ans<<endl;
}
return 0;
}
Donate comment here