POJ 3254 - Corn Fields

Description

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.

Input

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)

Output

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

Sample Input

2 3
1 1 1
0 1 0

Sample Output

9

Hint

Number the squares as follows: 
1 2 3
  4  

There are four ways to plant only on one squares (1, 2, 3, or 4), three ways to plant on two squares (13, 14, or 34), 1 way to plant on three squares (134), and one way to plant on no squares. 4+3+1+1=9.
? 题目大意:
       在一个 n*m(1<=n,m<=12) 的田地中种玉米,有些格子 不能放,要求不能有任何两个玉米相邻,问有多少种 方法(方法数 mod 100000000 )。

 这道题目一看很容易想到直观的搜索算法,但即使加上各种剪枝,也仍然会TLE。毕竟,搜索的复杂度达到2^nm。

那么,我们就要考虑一种动态规划的方法。如果列数确定为n的情况下,可以用dp[i][x1][x2]...[xn]这个n+1维状态来表示第i行的状态,后面的n维表示这列有没有种玉米(为0或1)。但是,状态转移方程要写出来也非常的复杂。

更何况,我们知道,列数并不是确定的。怎么办呢?

仔细想想,后面的这n维状态只有0或1两种可能性,不是可以用二进制把这n个01状态压缩为一个n位二进制数吗?

要知道,n最多只有12,2^12=4096种状态!!

get√

不过问题来了,就算把这些状态压缩了,那么状态之间又该如何转移呢?

我们用f[i][j]表示前i行中第i行种成j这种状态的可能方案数,那么就有:f[i][j]=sum(f[i-1][所有满足条件的k])

至于具体的条件如何判断?我们不妨回到题目,重新从题目来入手。

题目中提到了两个限制,一个是“指定的格子不能种”,还有一个是“相邻两个格子不能都种”。

在压缩成二进制的情况下,指定的格子不能种,只要知道该行哪些格子不能种(把输入数据压成二进制形式,g[i]表示第i行的限制,有哪些是不能种的),并且g[i] and 目标状态 = g[i]就说明可以这样种。

(也可以把g[i]先取反,变成0可以种1不能种,那么只要g[i] & 目标状态 = 0就可以种。)

至于“相邻的两个格子不能都种”也十分简单,列的相邻可以把原状态左移/右移一位并与原状态and一下,如果不为0那么就说明有相邻列都为1。行的相邻则是两行的状态and一下,不为0就是有相同列都种了。
到这里,问题基本就解决了。
 
    

#include
#include

using namespace std;

const int maxm = 12 + 5;
const int maxn = 12 + 5;
const int mod = 100000000;

int m, n;
int g[maxm]; //g数组为读入的数组,g[i]表示第i行的二进制的状态
int f[maxm][1 << maxn]; //f[i][j]表示第i行状态为j的可能性数

int num;
int data[1 << maxn]; //data[]用来存储行的每一种种法

void dp() {
for(int i = 0; i < num; i++) //初始化计算第1行的种法
if(!(g[0] & data[i])) f[0][i] = 1; //有些格子不能种的限制

for(int i = 1; i < m; i++) //以下的每一行
for(int j = 0; j < num; j++) //枚举第i行的每种状态
if(!(data[j] & g[i])) //有些格子不能种的限制
for(int k = 0; k < num; k++) { //与上一行每种状态检查
if(g[i - 1] & data[k]) continue; //(第i-1行的)有些格子不能种的限制

if(data[j] & data[k]) continue; //相邻两行同一列不能种的限制

(f[i][j] += f[i - 1][k]) %= mod; //累加方案数并取模
}

int ans = 0;

for(int i = 0; i < num; i++)
(ans += f[m - 1][i]) %= mod; //累加计算到最后一行的每种状态方案总数

cout << ans << endl;
}

int main() {
cin >> m >> n;

for(int i = 0; i < m; i++) //把第i行的状态转成二进制保存在g[i]
for(int j = 0; j < n; j++) {
int t;
cin >> t;
g[i] <<= 1;
g[i] |= !t; //为了方便位运算要统一先把不能种的0变成1
}

for(int i = 0; i < (1 << n); i++) //预先计算每行可能的状态
if(!(i & (i << 1))) data[num++] = i; //相邻两列同一行不能种的限制

dp();
return 0;
}

你可能感兴趣的:(解题报告,状压DP,POJ)