状态压缩DP解POJ3254-Corn Fields

本文内容转自:http://blog.csdn.net/harrypoirot/article/details/23163485?utm_source=tuicool&utm_medium=referral

对状态压缩DP有了初步的认识,自己做个记录,方便以后回忆时查看。有些自己当时不太好理解的地方加上注释。想从0开始看的童鞋先看上面的链接。

先贴代码,这是poj上的一道题的题解:http://poj.org/problem?id=3254 

先把自己的几点理解写在这里,大家可以先看下面代码,不明白的地方再翻回来看。

1. 状态数组state长度我设为378。是因为题目中要求N<=12,则令N=12,调用init()函数一算就可知top=377,最多有378个状态数,并且我把下标都改为从0开始了,强迫症啊。

2. 关于statte数组,当有N列数时(全是0或1,当成二进制数看),最大就是N个1,也就是(1<<N) - 1。这些个数中,有很多是相邻位都为1的,要剔除,比如3(11),state数组只保存了合符要求的。

3. cur[i]保存的为什么是第i行的逆,而不直接保存原数。cur表示的是实际的01数,state保存的是所有可能取的数。现在就是要判断每一个可能取的合理的数,是否满足当前牧场块分布(cur值)的要求。比如state取101时,如果人家牧场cur是110,肯定不行。二者怎么进行比较判断呢?直接相与,等于0肯定不可行,但不等于0不一定可行。这里有个逻辑就是cur必须把state的所有1覆盖(可以多出),才是可行的。如果将cur取逆,则cur为1的位是不可行的,相与等于1说明将1放在不该放的地方,肯定不可行;整个值相与为000说明不该放的没放,可以放的放不放无所谓了,都行的。这种做法很巧妙,背后的数学原理是什么,是什么逻辑运算?麻烦知道的告诉我。

4. 后面没什么好说的了。填dp[i][k]的时候是三层循环而不是二层。对于第i行来说,第k个状态取值不仅要和当前实际值cur[i]比较,比较可行后还要和上一行所有状态比较呢。

#include <cstdio>
#include <cstring>
using namespace std;

#define mod 100000000
int M, N, top = 0;
//top表示每行最多的状态数

int state[378];
//state存放每行所有的可行状态(即没有相邻的状态

int dp[20][378];
//dp[i][j]:对于前i行数据,每行有前j种可能状态时的解
int cur[20];
//cur[i]表示的是第i行整行的情况

inline bool ok(int x) {	//判断状态x是否可行
	if (x&x << 1)	return false;//若存在相邻两个格子都为1,则该状态不可行
	return true;
}
void init() {			//遍历所有可能的状态
	top = 0;
	int total = 1 << N; //遍历状态的上界
	for (int i = 0; i < total; ++i) {
		if (ok(i))state[top++] = i;
	}
}
inline bool fit(int x, int k) { //判断状态x 与第k行的实际状态的逆是否有‘重合’
	if (x&cur[k])return false; //若有重合,(即x不符合要求)
	return true;  //若没有,则可行
}
/*
此处,注意要用相反存储的数据来判断,
因为若10101001是一种可行状态,则可知101001也可行(是前者的一部分)
这时x即为10101001,cur[k]为10110,x&cur[k]=0,即符合条件
*/
int main() {
	freopen("in.txt", "r", stdin);
	while (scanf("%d%d", &M, &N) != EOF) {
		init();
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < M; ++i) {
			cur[i] = 0;
			int num;
			for (int j = 0; j < N; ++j) {  //输入时就要按位来存储,cur[i]表示的是第i行整行的情况,每次改变该数字的二进制表示的一位
				scanf("%d", &num);  //表示第i行第j列的情况(0或1)
				if (num == 0) //若该格为0	
					cur[i] += (1 << (N - j -1)); //则将该位置为1(注意要以相反方式存储,原因参看line34-36
			}
		}
		for (int i = 0;i < top;i++) {
			if (!(state[i]&cur[0])) {  //判断所有可能状态与第一行的实际状态的逆是否有重合
				dp[0][i] = 1;  //若第1行的状态与第i种可行状态吻合,则dp[0][i]记为1
			}
		}

		/*
		状态转移过程中,dp[i][k] =Sigma dp[i-1][j] (j为符合条件的所有状态)
		*/
		for (int i = 1; i < M; ++i) {  //i索引第2行到第M行
			for (int k = 0; k < top; ++k) { //该循环针对所有可能的状态,找出一组与第i行相符的state[k]
				if (!(state[k] & cur[i]))	//某状态符合第i行实际情况
				{
					for (int j = 0; j < top; ++j) { //找到state[k]后,再找一组与第i-1行符合,且与第i行(state[])不冲突的状态state[j]
						if (!(state[j] & cur[i - 1]) && !(state[k] & state[j]))		//符合第i-1行实际,并且i-1行和i行不冲突
							dp[i][k] = (dp[i][k] + dp[i - 1][j]) % mod;				//上一行中状态'j'下的情况累加到本行状态‘k'上
					}
				}
			}
		}
		int ans = 0;
		for (int i = 0; i < top; ++i) { //累加最后一行所有可能状态的值,即得最终结果!
			ans = (ans + dp[M-1][i]) % mod;
		}
		printf("%d\n", ans);
	}
	return 0;
}
2. POJ-1185,炮兵阵地。地址: http://poj.org/problem?id=1185 题解如下:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 101;
const int M=11;
const int MAXS = 61;
char matrix[N][M];
int dp[N][MAXS][MAXS];
int state[MAXS];
int num[MAXS];
int cur[N];
int top = 0, n = 0, m = 0;

int getnum(int i) //求某状态二进制数1的个数
{
    int ret = 0;
    while(i)
    {
        i &= i - 1;
        ++ret;
    }
    return ret;
}
void initstate()
{
    top = 0;
	int total = 1<<m;
    for(int i = 0; i < total; ++i)
    {
        if(!(i&(i<<1)) && !(i&(i<<2)))
        {
            state[top] = i;
            num[top++] = getnum(i);
        }
    }
}
int main()
{
	freopen("in.txt", "r", stdin);
    int i = 0, j = 0, k = 0, t = 0;
    while(EOF != scanf("%d%d", &n, &m))
    {
        for(i = 0; i < n; ++i)
        {
            cur[i] = 0;
            scanf("%s", matrix[i]);
            for(j = 0; j < m; ++j)
            {    
                if('P' == matrix[i][j])
                {
                    cur[i] += (1<<(m-j-1));
                }
            }
        }
        for(i = 0; i < n; ++i)
            for(j = 0; j < MAXS; ++j)
                for(k = 0; k < MAXS; ++k)
                    dp[i][j][k] = -1;
        initstate();
        for(i = 0; i < top; ++i)
            if(!(state[i]&(~cur[0])))
                dp[0][i][0] = num[i];
       //dp[i][j][k] = max{dp[i][j][k], dp[i-1[k][t]]+num[j]}
       for(i = 1; i < n; ++i)
       {
           for(j = 0; j < top; ++j)// state of row i
           {
               if((~cur[i])&state[j])
                   continue;
               for(k = 0; k < top; ++k)//state of row i-1
               {
                   if((~cur[i-1])&state[k])
                       continue;
                   if(state[j]&state[k])
                       continue;
                   for(t = 0; t < top; ++t)//state of row i-2
                   {
                     if(state[j]&state[t]) //不用再判断state[k]与state[t]了,求i-1行时已经成立
                         continue;
                     if(-1 == dp[i-1][k][t])
                         continue;
                     dp[i][j][k]=max(dp[i][j][k], dp[i-1][k][t]+num[j]);
                   }
               }
           }
       }
       int ret = 0;
       for(i = 0; i < top; ++i)
       {
           for(j = 0; j < top; ++j)
           {
               if(dp[n-1][i][j]>ret)
                   ret = dp[n-1][i][j];
           }
       }
       printf("%d\n", ret);
    }
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值