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不能放 。