本来就是一个写不大来动态规划的人,结果现在又了解到还有种东西叫状态压缩dp,唉。。。

找了一道例题来试试看:http://www.lydsy.com/JudgeOnline/problem.php?id=1725

解:我们用f[i][j]来表示当前第i行,状态为j的情况下,(且之前的1~i-1的方案数已经确定了),前i行有多少种方案,那么动态转移方程其实很明显:

f[i][j]=sum(f[i-1][k]);(当上一行为第k种状态且与当前枚举的第i种状态不会产生相邻的土地,就加上f[i-1][k]的方案数)

方程好写,可是状态很难表达啊,我们总不能开一个12维的数组吧。。。

所以我们就利用了它的表达要么是0,要么是1,的这个特点用二进制解决。

首先我们来看样例数据,和如何预处理:

输入:

2 3
1 1 1
0 1 0
输出:

9
scanf("%d%d",&m,&n);
  for (int i=1;i<=m;i++)
   for (int j=n;j>=1;j--)
   {
        scanf("%d",&now);
        if (now==1) a[i]=a[i]|(1<<(j-1));
   }

这段代码就是把输入数据当成二进制并转换成十进制,如第一行储存为7,第二行储存为2。方便接下来的操作

sum=(1<<n)-1;
   for (int j=0;j<=sum;j++)
    if (((a[1]|j)==a[1])&&((j&(j<<1))==0))  dp[1][j]++;

哈这段代码就蛮重要的啦

dp[i][j]之前就讲过了它的意思,那么j状态具体指什么呢??

比如dp[2][6]就表示我们把第二行种成1 1 0这种样子能获取的方案数,将j转换成2进制,就形象的代表了一种种草的形态。

如j为2是表示0 1 0这种状态。

那么我们就先预处理第一行在各种种草状态下是否符合题目要求,枚举j这种状态,须符合以下两点才是合法的:

1.这种 状态本身不能有两个相邻的1,比如说1 1 0就不可以因为这样把两块草场种在了一起。

//具体实现方法,我们把原本状态与当前枚举状态取或,如果说a[i]的值变化了,说明它有哪块地方原本为0,不允许种,但是当前情况却种了,导致那块草场变为了1

2.这种状态要符合开始的输入,比如说我们枚举第二行为1 0 0就是不可以的,因为它原本是0 1 0,第一位是不允许种草的,咱不能硬种啊。。。

//将当前状态左移或者右移(这个随便你),再与没移之前的状态进行and,如果答案为0,说明它这个状态没有相邻的可种草场,否则一定会有哪一块是1.

符合以上两点,方案数就可以加1;

然后我们正式进行dp,枚举当前的行与状态(请保证它是合法的,和第一行的判断方法一样),并且枚举上一行的状态,两个状态and一下,如果合法就加上上一行这种情况下的方案。

恩,大概就是这样,发觉自己讲的好乱,,,,

那下面是程序:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int m,n,now,sum;
int a[100],dp[20][10000];
int main()
{
  scanf("%d%d",&m,&n);
  for (int i=1;i<=m;i++)
   for (int j=n;j>=1;j--)
   {
        scanf("%d",&now);
        if (now==1) a[i]=a[i]|(1<<(j-1));
   }
  sum=(1<<n)-1;
   for (int j=0;j<=sum;j++)
    if (((a[1]|j)==a[1])&&((j&(j<<1))==0))  dp[1][j]++;
  for (int i=2;i<=m;i++)
   for (int j=0;j<=sum;j++)
   if (((a[i]|j)==a[i])&&((j&(j<<1))==0))
   {
        for (int k=0;k<=sum;k++)
        if ((j&k)==0) dp[i][j]=(dp[i][j]+dp[i-1][k])%100000000;
   }
   int ans=0;
   for (int j=0;j<=sum;j++) ans=(ans+dp[m][j])%100000000;
   cout<<ans<<endl;
   return 0;
}

 

posted on 2017-02-25 15:17  nhc2014  阅读(311)  评论(0编辑  收藏  举报