POJ-3254 状压DP

http://poj.org/problem?id=3254

第一道状压DP

题目大意:一个老农,有一块n行m列由01组成的田地,现在他有一窝牛无处可放。 1的田地代表可放牛,0的地代表不可放牛,上下和同一行都不能有相邻。 全是0算一种情况。  求一共有多少种放法(话说人一旦吃饱了真是可怕)。


一道状压DP模板题,他的提升可见 1185(炮兵阵地)。

首先恶补一下位运算,因为需要用位运算的技巧快速判断当前状态是否符合规定

状压DP(状态压缩动态规划)思想:保留了DP的利用上一状态的最优求当前状态的最优直至求全局最优的性质。 在此基础上,增加了状态压缩。所谓状压,指一串由01组成的串,将他转换为十进制的数值表示当前状态。

例如:000  状态0

          001  状态1

          100  状态4。

如果01串的长度为m,那么就有2^m(1<<m)种状态  从0....0  到 1....1 。 用每一种01组合表示一种状态,再讲01组合转为十进制存在数组中,即可求解一些问题。虽然是DP,但是由于状态之多,所以是一种变相的大暴力

解题思想:当前行的当前状态放置数等于 加上前一行的各种状态的放置数。

此题例子:    1 1 1

                     0 1 0    



能放的地方为1的地方。所以手写列出所有状态。

第一行的状态表:

组合方式

状态号

000

0

001

1

010

2

100

4

101

5

第2行的状态表:

组合方式

状态号

000

0

010

2

第二行开始,以000为基础   第一行符合条件的有5种。

                    以010为基础,第一行除状态2之外都符合。

总数:5+4=9



用dp(i,j) 表示第i行状态j的放置数量。   状态转移方程: dp(i,j) = dp(i,j) + dp(i-1,k)  k为在状态j的基础上寻找已知的上一行的所有符合规定的状态。

做DP 从头开始想   因为涉及到上一行 所以第一行从1开始,第0行默认0,但是dp[0][0] = 1 即状态0是一种情况。

这样,dp[1][0....2^m] 加上前一行的默认0 求出了当前行所有状态的问题解。

          dp[2][0....2^m]   举个例子: dp[2][1] 第2行状态为1的时候即001   然后开始寻找第1行的所有状态(已经求出最优),讲符合条件的加到dp[2][1]中。以此类推。


AC代码:

#include <iostream>
#include <cmath>
#include <string>
#include <memory.h>
#include <vector>
using namespace std;
typedef long long int LL;

#define line 20
#define col 20
#define _for(i,a,b) for(int i = (a); i < (b); i ++)
#define mod 100000000  

int m,n;
int dp[line][1<<13];  //每一列都有2^m种情况    
int a[line];		  //每一行的初始放置规定,用来判断一种状态是否符合能放的地方放。   

bool isZero(int i, int j){
	//判断规定的放置条件和当前是否冲突
	if((a[i]&j) != j)
		return false;
	//判断相邻是否冲突
	if( (j & (j<<1)) != 0)
		return false;
	return true; 
}
int solve(){
	
	memset(dp,0,sizeof(dp));
	dp[0][0] = 1;			//第0行只有状态0 无条件是1种情况
	
	for(int i = 1 ; i <= n ; i ++){
		
		for(int j = 0 ; j < (1<<m) ; j ++){
			if( !isZero(i,j) )
				continue;
			for(int k = 0 ; k < (1<<m) ; k ++){
				//判断相邻两行是否冲突 
				if((j&k) != 0)
					continue;
				dp[i][j] += dp[i-1][k];
				//题目要求求模 
				dp[i][j] %= mod;
			}
		}
	}
	int ans = 0; 
	//遍历最后一行所有状态 
	for(int i = 0 ; i < (1<<m) ; i ++){
		ans += dp[n][i];
		ans %= mod;
	}
	
	return ans;

}

int main()
{
	int k;
	cin >> n >> m;
	for(int i = 1; i <= n ; i ++){
		a[i] = 0;
		for(int j = 1; j <= m ; j ++){
			cin >> k;
			//每一行的状态总数 
			a[i] = (a[i]<<1) + k;
		}
	}
	int ans = solve();
	cout << ans << endl; 
}

比较容易出错的是没有判断当前状态是否符合田地的放置规定,即1能放0不能放    。

猜你喜欢

转载自blog.csdn.net/a673786103/article/details/80286427
今日推荐