poj3254-Corn Fields(浅谈及详谈状压dp)

版权声明:转载请告知博主并要注明出处嗷~ https://blog.csdn.net/Akatsuki__Itachi/article/details/82381682

前两天打了一场比赛,状压不会,再次发现知识短板,哭...

第一次学的时候是大一学的,但是失败了,没学会,后来心理阴影总感觉很难,就没再学过。

这两天又重新学了一下。

谈一谈入门题~poj3254

题意:有一个n*m的0-1矩阵草地,1代表在这里可以放牛,0代表不能放牛。每两头牛不能相邻(左右相邻或者上下相邻),问有多少种放牛的方法。


直接进入正题:

(以下皆为学习完状态压缩后的personal comprehension,如若不对,敬请大佬指出~)

简单的说,状态压缩就是把问题的状态用一个十进制表示。一般的,问题的状态只有两种,走或者不走,选或者不选,放或者不放,我们都可以用0和1来表示这两种状态,但是如果单单用0和1来表示问题所有的状态,那么所涉及到的情况会有很多,所以我们把它转化为十进制,并且用 按位与,按位或,异或,左移右移等这些操作,可以方便且快速的得到问题的状态和结果。


回到问题,对于样例:

1 1 1

0 1 0

我们发现,总共有三列,也就是说,对于每一行,假设这一行所有的草地都能放牛,即输入为1 1 1的时候(样例的第一行),

我们可以得到下面

0 0 0; 0 0 1;0 1 0;1 0 0;1 0 1 五种情况,它分别对应十进制 

   0           1         2         4          5

也就是说,对于m=3的时候,每行放牛的情况最多就只有 这五种,我们先不要去想整个矩阵,只需要单独考虑一行,因为我们可以在后续的操作中通过和上一行的状态比较,来删除掉一些不符合情况的状态。

即:对于输入的m最多有(2^m)- 1 种状态,这些状态是可以预处理出来的,只需要让两个1不相邻即可,如下:

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

我们已经分析完了第一行的状态,一共有五种。

那么我们看第二行的输入为 0 1 0,值为0的草地不能放牛,所以这一行只有两种状态,即:

0 0 0 ; 0 1 0,分别代表十进制

   0            2

这样,我们就可以拿这一行的每个状态和上一行的每个状态比较,去除掉上下相邻的情况,得到最终的答案。

dp[ i ] [ state[j] ] 为对于第 i 行,第state[j]中状态时,放牛的方案总数、

那么对于每一行的状态state[j],我们都可以从上一行所有的状态里得到。

即动态转移方程为:

dp[j][state[j]]=dp[i-1][state[1]]+dp[i-1][state[2]]+...+dp[state[len],其中len为所有状态总数。

当然,其中还有不满足条件的,我们还需要加判断(详见代码)

到这里,状态压缩的过程基本就算完了。

综上所述,对于这道题,解决问题有如下三步:

第一步:预处理所有可能的状态:

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

第二步:统计出每行所有的状态。

其实这道题,把1设为可放牛,0设为不可放牛会更好理解些,不过问题不大,只是状态的一个逆。

怎么得到这一行所有的状态呢?举个栗子:

要得到第二行的状态,即 0和2,现在我们知道所有的状态有0 1 2 4 5.

当我们输入到第二行的时候,如我们输入的是 0 1 0

我们可以看成是1 0 1,然后把这个数转为十进制->5

用5(1 0 1)&按位与(所有的状态),如果得到的结果为不为0,那么肯定不符合条件。

比如上面按位与之后

      0 0 0       0 0 1       0 1 0       1 0 0       1 0 1

&   1 0 1       1 0 1       1 0 1        1 0 1       1 0 1

=    0 0 0       0 0 1       0 0 0        1 0 0       1 0 1

我们只取结果为0的状态,即 0 0 0 和 0 1 0,就是上面我们所提到的符合条件的状态。

//输入处理
for(int i=1; i<=n; i++)
{
     row[i]=0;
     for(int j=1; j<=m; j++)
     {
         int x;
         scanf("%d",&x);
         if(!x)
             row[i]+=1<<(m-j);
      }
}

第三步:预处理第一行的方案数

这个就不多说了

for(int i=0; i<len; i++)
    if(judge(state[i],1))//judge函数判断第一行的状态是否符合所有的状态中的某个状态
        dp[1][i]=1;

第四步:去除掉不满足条件的状态,统计对于当前状态所有的方案数。

鉴于题目中要求上下也不能相邻,所以我们还需要加一个判断条件来去掉上下相邻的情况,这个很简单,只需要按位与上两个状态,结果为0就符合条件,否则不符合。

if(state[k]&state[j])
    continue;//不符合条件

所以总的代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#define max(a,b)   (a>b?a:b)
#define min(a,b)   (a<b?a:b)
#define swap(a,b)  (a=a+b,b=a-b,a=a-b)
#define memset(a,v)  memset(a,v,sizeof(a))
using namespace std;
typedef long long int LL;
const int MAXL(1e4);
const int INF(0x3f3f3f3f);
const int mod(1e8);
int state[MAXL+50];
int row[20];
int dp[20][MAXL+50];
int n,m,len;
void init()//初始化
{
    len=0;
    for(int i=0; i<(1<<m); i++)
        if((i&(i<<1))==0)
            state[len++]=i;
}
bool judge(int x,int i)//判断每一行的状态是否符合所有状态中的某个状态
{
    if(x&row[i])
        return false;
    return true;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        memset(dp,0);
        for(int i=1; i<=n; i++)
        {
            row[i]=0;
            for(int j=1; j<=m; j++)
            {
                int x;
                scanf("%d",&x);
                if(!x)
                    row[i]+=1<<(m-j);//输入处理
            }
        }
        for(int i=0; i<len; i++)//第一行初始化
            if(judge(state[i],1))
                dp[1][i]=1;
        for(int i=2; i<=n; i++)
        {
            for(int j=0; j<len; j++)
            {
                if(!judge(state[j],i))//如果不符合第二行的状态,那么继续找下一个状态
                    continue;
                for(int k=0; k<len; k++)
                {
                    if(state[k]&state[j])//判断上下是否相邻
                        continue;
                    dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;//求和
                }
            }
        }
        LL ans=0;
        for(int i=0; i<len; i++)
            ans+=dp[n][i],ans%=mod;//最后一行所有的状态的方案的总和即为最终答案
        cout<<ans<<endl;
    }
}

写了好久,洗澡睡觉~

猜你喜欢

转载自blog.csdn.net/Akatsuki__Itachi/article/details/82381682