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不能放 。
本文详细介绍状态压缩动态规划(状压DP)的基本概念、核心思想及应用技巧,并通过一个具体的农场放牛问题实例,展示如何运用状压DP解决实际问题。
1164

被折叠的 条评论
为什么被折叠?



