状压DP详解(1)-从状压到状压DP+简单例题玉米田Corn Fields-POJ3254

emmm,首先要搞懂状压DP这个东西的时候我们要搞懂状压这个概念,其实就是二进制运算的概念,比较经典的就是我写的一个状压非DP–的题目Even Parity—Uva11464—偶数矩阵:
这是我对与状压非DP的一个做法https://blog.csdn.net/qq_43906000/article/details/90798220
当然,在里面应该就可以搞懂状压这个概念了,简单来讲就是暴力枚举,二进制枚举一行的状态然后进行处理。

直接拿一道题目来讲吧:poj3254玉米田,这里直接给出题面了:

Corn Fields

Time Limit: 2000MS Memory Limit: 65536K

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.


题目大意:给你一块田,只能在1的地方中玉米,然后每个玉米的前后左右都不能有玉米,问你有几种种法。

按照我们刚刚的讲法,先暴力枚举,只不过这一题题目和偶数矩阵有些不太一样,这一题我们要枚举每一层的状态,这样才能算他的方案数(每行的每一种可行状态就算一种方案了)于是我们就暴力了(从0开始比较方便):

for (int i=0; i<n; i++)  //枚举行
	for (int j=0; j<(1<<m); j++)  //枚举该行的状态

就是从状态0000000全部都不种的状态,到1111111全部都种的状态。

然后对于该行的状态我们要判定他是否符合输入的文本,而且它是否满足隔着一个再放的约束条件,那么我们的代码就可以扩充了:

for (int i=0; i<n; i++)  //枚举行
	for (int j=0; j<(1<<m); j++)  //枚举该行的状态
		if (!ok(i,j)) continue;//该行的这个状态不可行

//其中ok函数如下:
int ok(int r,int s)
{
	for (int i=0; i<m; i++){//判断是否符合文本
		if ((s&(1<<i)) && !a[r][i]) return 0;
	}
	for (int i=0; i<m; i++){//判断是否隔一个再放的条件
		if ((s&(1<<i)) && (s&(1<<(i+1)))) return 0;
	}
	return 1;
}

这里解释一下‘&’:1&1=1,1&0=0,这里可以直接判断s的第i位是否为1;
第一行就直接在continue之后加dp[i][j]=1;就好了,当然加个判断是必要的,接下来就是对下面行数的处理了,下面的行数不仅仅有本行数放置的状态数,我们将前一行的状态数也放进来这样才是总的方案数:

for (int k=0; k<(1<<m); k++) { //枚举上一行的状态
	if ((j&k)==0) dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
}

其中j是该行的状态,我们枚举上一行的状态,看看是否能满足约束条件,(j&k)==0也就是指j和k的0和1是相互错位的,也就是指状态成立。那么我们就是将本行的这个j状态和所有上一行可匹配的状态k放到dp中进行下一行的准备。

那么最后一行的每个状态之和就是我们的方案总数了,以下是AC代码(注意POJ是不支持万能头文件的哦(* ^ ▽ ^ *)):

//#include <bits/stdc++.h>
#include <cstdio>
using namespace std;
typedef long long ll; 
const ll mod=1e8;
int a[15][15],b[15][15];
ll ans=0,dp[15][1<<15],m;
int ok(int r,int s);
int main()
{
	int n;
	scanf ("%d%d",&n,&m);
	for (int i=0; i<n; i++){
		 for (int j=0; j<m; j++){
		 	scanf ("%d",&a[i][j]);
		 }
	}
	for (int i=0; i<n; i++){//枚举行 
		for (int j=0; j<(1<<m); j++){//枚举该行的状态 
			if (!ok(i,j)) continue;
			if (i==0) {
				dp[i][j]=1;
				continue;
			}
			for (int k=0; k<(1<<m); k++){//枚举上一行的状态 
				if ((j&k)==0) dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
			}
		}
	}
	for (int i=0; i<(1<<m); i++){
		ans=(ans+dp[n-1][i])%mod;
	}
	printf ("%lld\n",ans);
	return 0;
}
int ok(int r,int s)
{
	for (int i=0; i<m; i++){
		if ((s&(1<<i)) && !a[r][i]) return 0;
	}
	for (int i=0; i<m; i++){
		if ((s&(1<<i)) && (s&(1<<(i+1)))) return 0;
	}
	return 1;
}

当然,有一种更快一点的办法就是先将所有符合约束条件的数存下来然后枚举,之后我们就不用判断它的每行是否符合约束条件了,只不过这个比较不直观,不太好理解,所以也不太建议初学者这么写。。。。在我的博客状压DP详解(2)中有具体介绍。

上一篇:ElasticJob corn定时表达式语法(亲测)


下一篇:基类、子类之间的类型转换