您的位置:首页 > 其它

[状态压缩DP] Poj 3254, Poj 1185

2012-07-23 11:45 387 查看
    状态压缩DP一般适合的题型的特征为:一个矩阵,行数较大,列数较小,每个点的状态只有两种。正好用一个整型数int来表示每行的一种状态(其实是其二进制形式,每bit的0和1表示每点的状态)。不同的是各题的状态dp的定义,状态间的限制,状态的转换方程。

第一道(Poj 3254):

/**
题意:在一片M行N列的草地上(用0和1矩阵表示),1表示能放牛,0表示不能放。
在草地上放牛并且牛不能相邻,问有多少种放法(一头牛都不放也算一种)。

题解:对于每一行来说,放牛的可能数有2^N种,但是根据以下限制条件就能排除很多:
1.每行中的牛不能相邻,经计算当N=12时,满足条件的状态只有377个
2.每行中放牛要满足草地的硬件条件,只有1处可放,排除一些
3.上一行中与本行对应的位置处不能放牛,排除一些

由于N值最大为12,所以可以用一个二进制数来表示一行的状态,这就是“状态压缩”了。
定义dp[i][j]:第i行的状态为state[j]时,前i行能放牛方法的总数.
*/

#include <cstdio>
#include <queue>
#include <algorithm>
#include <iostream>
#include <cstring>

using namespace std;

#define MOD 100000000

int n, m, row[12];
int nState, state[1000];
int dp[14][1000];

void init()
{
int k = 1 << n;
nState = 0;
for (int i = 0; i < k; i++)
if ( (i & (i<<1)) == 0 )
state[nState++] = i;
}

int main()
{
int k;

scanf("%d %d", &m, &n);
init();
for (int i = 0; i < m; i++)
{
row[i] = 0;
for (int j = n-1; j >= 0; j--)
{
scanf("%d", &k);
row[i] += k << j;
}
}

// 求dp[0],若state[j]满足第0行草地的硬件条件,则dp[0][j]=1
for (int j = 0; j < nState; j++)
{
dp[0][j] = ( (row[0] & state[j]) == state[j] ) ? 1 : 0;
}

// 求dp[i],如果state[j]和第i-1行的状态state[k]不冲突,则dp[i][j]=Σ(dp[i-1][k])
for (int i = 1; i < m; i++)
{
for (int j = 0; j < nState; j++)
{
if ( (row[i] & state[j]) != state[j] )  //判断是否满足草地硬件条件
continue;
for (int k = 0; k < nState; k++)
{
if ( dp[i-1][k] && (state[k]&state[j]) == 0 )
dp[i][j] = (dp[i][j] + dp[i-1][k]) % MOD;
}
}
}

int res = 0;
for (int j = 0; j < nState; j++)
{
if ( dp[m-1][j] )
res = (res + dp[m-1][j]) % MOD;
}
printf("%d\n", res);

return 0;
}


第二道(Poj 1185):

/**
题意:在一个n行m列的矩阵中,字符'P'处能放炮兵,字符'H'处不能放炮兵。
并且如果两个炮兵在一条(水平或垂直)直线上时,它们的距离不能小于2,问最多放多少个兵。

由于在求第i行时,它的状态要收到第i-1行和i-2行的影响,所以定义一个三维dp:
dp[i][j][k]表示第i行的状态为state[j],第i-1行的状态为state[k]时,前i行能放炮兵的最大数量。

for ( ... i < n ...)
{
for ( ... j < nState ... )
{
如果状态j和第i行的地形不冲突,那么:
for ( ... k < nState ... )
{
如果第i行状态j和第i-1行状态k不冲突,那么:
for ( ... h < nState ... )
{
如果第i行状态j和第i-2行状态h不冲突,那么:
在dp[i-1][k][h]中找最大的一个赋值给dp[i][j][k],再加上state[j]中1的个数
}
}
}
}

*/

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int n, m, dp[105][80][80];      //dp[i][j][k]表示第i行的状态为j,第i-1行的状态为k时能放炮兵的最大数量

int nState, state[80], num[80]; //10位二进制位中各个1之间的距离不小于2,这样的数只有60个。
//依次存放在state[]中,num[i]表示state[i]中1的个数

//在小于2^m的数中找1之间的距离不小于2的数,保存在state[]中
void init()
{
int k = 1 << m;
nState = 0;
for (int i = 0; i < k; i++)
if ( (i&(i<<1)) == 0 && (i&(i<<2)) == 0 )
{
state[nState] = i;
num[nState] = 0;
int j = i;
while (j)
{
num[nState] += j % 2;
j /= 2;
}
nState++;
}
}

int main()
{
int  row[105];
char str[15];

while ( cin >> n >> m )
{
init();
for (int i = 0; i < n; i++)
{
row[i] = 0;
scanf("%s", str);
for (int j = 0; j < m; j++)
if (str[j] == 'P')
row[i] += 1 << j;
}

memset(dp, 0, sizeof(dp));

// 计算dp[0]
for (int j = 0; j < nState; j++)
{
if ( (state[j] & row[0]) != state[j] )
continue;
for (int k = 0; k < nState; k++)
dp[0][j][k] = num[j];
}

// 计算dp[1]
if (n > 1)
for (int j = 0; j < nState; j++)
{
if ( (state[j] & row[1]) != state[j] )
continue;
for (int k = 0; k < nState; k++)
{
if ( (state[j] & state[k]) == 0 )
dp[1][j][k] = dp[0][k][0] + num[j];
}
}

// 计算dp[>1]
for (int i = 2; i < n; i++)
{
for (int j = 0; j < nState; j++)
{
if ( (state[j] & row[i]) != state[j] )
continue;
for (int k = 0; k < nState; k++)
{
if ( state[j] & state[k] )
continue;
for (int h = 0; h < nState; h++)
{
if ( state[j] & state[h] )
continue;
if ( dp[i-1][k][h] > dp[i][j][k] )
dp[i][j][k] = dp[i-1][k][h];
}
dp[i][j][k] += num[j];
}
}
}

// 在dp[n-1]中找最大值
int max = 0;
for (int j = 0; j < nState; j++)
{
for (int k = 0; k < nState; k++)
if (max < dp[n-1][j][k])
max = dp[n-1][j][k];
}

printf("%d\n", max);
}
}
---------------------- 更多“状态压缩DP”题目... ------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: