DP之状态压缩

https://www.cnblogs.com/liwenchi/p/5849124.html

Corn Fields

在这个链接写得不错,

先说说地图,地图上每一行的01代表一个状态,比如输入样例中的111、010,表示第一行的三个位置都可以种稻子,第二行中间的位置可以种稻子,然后,不能种稻子的地方一定不能种稻子(废话...)

可以种稻子的地方可以选择种也可以选择不种,然后有一个前提条件,就是上下左右相邻的地方不能种稻子。

  再说说怎么状态压缩,状态压缩就是把每一个状态压缩成二进制,二进制就是由01组成的,0代表不种,1代表种。二进制就要牵扯到位运算,位运算我就不想说了,百度吧。因此,一串01的二进制数就

可以代表一个状态,例如输入样例第一行是111,那么可以放入第一行的状态有,100、010、001、101、000,因为相邻位置不能放所以只有5种方法,那么第二行就只有2种方法000、010(不考虑其他行)

  那么看第一行和第二行(第一行——第二行),100——000,010——000,001——000,101——000,000——000,这是5种对应方法,还可以100——010,001——010,101——010,000——010这是另外的4种对应方法(第一行5种状态对吧?第二行2种状态,按照乘法原理,应该有5*2 = 10种方法,但是111——010是不合法的,因此样例的答案是10-1 = 9)。

dp[i][j]意思是推到第i行状态为j的方案总数。

那么“100——000”即为dp[2][000]可以由dp[1][100]得到,那么dp[2][000] = dp[2][000] + dp[1][100];

那么“010——000”即为dp[2][000]可以由dp[1][010]得到,那么dp[2][000] = dp[2][000] + dp[1][010];

......

以此类推,逐行递推。

总结一下思路:先枚举第一行,把所有可能的状态和第一行的地图对比,如果成功,则在循环里继续枚举第二行,把所有可能的状态和第二行的地图对比,如果成功,再和第一行填入的状态对比,如果又匹配成功,则dp[2][000] = dp[2][000] + dp[1][100];方法数加到第二行。这就是一次循环结束了,从新枚举第二行...


为了更好的理解状压dp,首先介绍位运算相关的知识。

1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。

2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。

3.’^’符号,x^y,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)^2(10)=1(01)。

4.’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。相应的,’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。

这四种运算在状压dp中有着广泛的应用,常见的应用如下:

1.判断一个数字x二进制下第i位是不是等于1。

方法:if ( ( ( 1 << ( i - 1 ) ) & x ) > 0)

将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。

2.将一个数字x二进制下第i位更改成1。

方法:x = x | ( 1<<(i-1) )

证明方法与1类似,此处不再重复证明。

3.把一个数字二进制下最靠右的第一个1去掉。

方法:x=x&(x-1)


代码1

此代码是在num==0时来记录每行的状态的。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define mod 100000000
using namespace std;
int dp[13][1<<12],cur[13];
int can[1<<12],tot,m,n;

int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        tot = 0;///全局变量,相当于栈的top,代表可行的状态数
        for(int i=0;i<(1<<n);i++)///n是列数,i是枚举的状态
            if((i&(i<<1))==0) can[tot++] = i;
        memset(cur,0,sizeof(cur));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<n;j++)
            {
                int num;
                scanf("%d",&num);
                if(num==0) cur[i] = (cur[i]|(1<<j));
                ///这里要给0的地方变为1,1的地方放上0,因为要保证不合法的匹配一定是独一无二的。自己思考一下吧
            }
        }
        for(int i=0;i<tot;i++)///初始化,和cur[1](第一行)匹配,就给对应的dp赋值为1
            if((cur[1]&can[i])==0) dp[1][can[i]] = 1;
        for(int i=1;i<m;i++)///枚举每一行
        {
            for(int j=0;j<tot;j++)///对第i行枚举所有可行的状态j
            {
                if((can[j]&cur[i])==0))///如果状态j和第i行匹配了
                {
                    for(int k=0;k<tot;k++)///枚举第i+1行的所有可行的状态k
                    {
                        if(((can[k]&cur[i+1])==0)&&((can[k]&can[j])==0))///状态k和第i+1行匹配且和状态j匹配
                            dp[i+1][can[k]] = dp[i+1][can[k]]+dp[i][can[j]];///状态数相加
                    }
                }
            }
        }
        int ans = 0;
        for(int i=0;i<tot;i++)
        {
            ans += dp[m][can[i]];
            ans = ans % mod;
        }
        printf("%d\n",ans);
    }
}

代码2

此代码是num==1时来存储此行的状态的。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define mod 100000000
using namespace std;
int dp[13][1<<12],cur[13];
int can[1<<12],tot,m,n;

int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        tot = 0;///全局变量,相当于栈的top,代表可行的状态数
        for(int i=0;i<(1<<n);i++)///n是列数,i是枚举的状态
            if((i&(i<<1))==0) can[tot++] = i;
        memset(cur,0,sizeof(cur));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<n;j++)
            {
                int num;
                scanf("%d",&num);
                if(num==1){
                    cur[i]+=(1<<m-j); ///不用变,直接存储
                }
            }
        }
        for(int i=0;i<tot;i++) ///初始化dp第一行
            if((can[i]|cur[1])==cur[1]) ///若或运算之后的结果还跟以前的一样,说明可行,例如101 |010 结果111,不可行
                dp[1][can[i]]=1;
        for(int i=2;i<=m;i++)
        {
            for(int j=0;j<tot;j++)
            {
                if((can[j]|cur[i])==cur[i]){///判断是否与第i行的状态有冲突
                    for(int k=0;k<tot;k++){
                        if(((can[j]&can[k])==0))///不判断第i+1行与状态j是否有冲突,
                            ///判断第i行状态与第i+j行的状态是否有冲突
                           dp[i][can[j]]=dp[i][can[j]]+dp[i-1][can[k]];///假设can[k]与cur[i-1] 有冲突,加起来也不影响,自己细想下就能理解,只不过循环次数多一点而已
                        printf("dp[%d][%d]=%d\n",i,can[j],dp[i][can[j]]);
                    }
                }
            }
        }
        int ans = 0;
        for(int i=0;i<tot;i++)
        {
            ans += dp[m][can[i]];
            ans = ans % mod;
        }
        printf("%d\n",ans);
    }
}

猜你喜欢

转载自blog.csdn.net/ljd201724114126/article/details/80406598
今日推荐