本来就是一个写不大来动态规划的人,结果现在又了解到还有种东西叫状态压缩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; }