【算法学习笔记】状态压缩dp

 在acwing上学习算法的一点思考与总结


在状态压缩中,我们使用整数的二进制表示来表示一个集合的状态,以减少内存消耗和提高计算效率。下面解析一道经典的状态压缩dp的题。

思路 

如何解:摆放方块的时候,先放横着的,再放竖着的。总方案数等于只放横着的小方块的所有合法方案数。合法的意思是在一列中必须要有能放得下竖直长方形的位置,其次在放横着的长方形时,i与i-1列不能有所交叠。

核心:第i列的摆放状态只与第i-1列的摆放状态相联系,这意味着我们在考虑第计算第i列的合法方案数时只需要考虑第i-1列的状态就行了。

二进制表示状态:为了表示一列里面的摆放方式,我们可以用二进制来枚举他所有可能的摆放方式。(这里的摆放方式只有横放)。比如1100,表示第i列的前两行都有从i-1横着放过来的长方形,后两行则为空。那么一列中会有2^n种状态。

状态转移方程:所以我们的状态表达式f[ i ][ j ]的含义就是第 i 列的 j 状态(j是十进制数)。状态转移方程就是将符合合法状态的方案数累加: f [ i ][ j ] += f [ i - 1 ][ k ] (将第i列j状态的合法方案数与前一列k状态的合法方案数累加)

判断合法:1.用位运算得到二进制的第i位数,记录连续个零的个数

                  2.枚举两列不同状态组合下是否满足两个条件

                      第一个条件:第i-2列伸出来的 和第i-1列伸出来的不冲突(不在同一行) 
                      第二个条件:   第i-1列此时的状态是否合法

 

 

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 13,  M = 1 << N;
long long f[N][M]; //第一维表示列,第二维表示所有可能的状态
bool st[M]; //该状态是否存在连续奇数个0
int n, m;
vector<int > state[M]; //存放第i列与第i-1列合法状态的集合,用一个二维数组储存。

int main()
{
    while(cin>>n>>m, n || m) //当n,m都为零时退出循环
    {
        for(int i = 0; i < (1<<n); i ++ )
        {    
            int cnt = 0; //记录连续个零的数量
            bool valid = true;
            
            for(int j =0; j < n; j++)
                if((i >> j) & 1) // i >> j 表示i的二进制数的第j位,如果第j位是1,需执行下面的判断
                {    
                    if(cnt & 1)  //如果前面零的数量为奇数,则该列状态不合法
                    {
                        valid = false; //标记为false
                        break;
                    }
                    cnt = 0; //相反如果数量是偶数,则将cnt清零
                }
                else cnt ++;
            if(cnt & 1) valid = false; //将剩下的零的数量进行判断
            st[i] = valid; //将该状态的合法情况录入st数组
        }
    

        //寻找相邻两列合法时的i和i-1状态的组合
        for(int j = 0; j < (1<<n); j ++) //枚举第i列状态
        {    
            state[j].clear(); //清空上次操作遗留的状态,防止影响本次状态
            for(int k = 0; k< (1<<n); k ++) //枚举第i-1列状态
                if(( j & k) == 0 && st[j | k] ) 
                //第一个条件:第i-2列伸出来的 和第i-1列伸出来的不冲突(不在同一行) 
                //第二个条件: 第i-1列的此时状态是否合法
                    state[j].push_back(k); //将合法情况加入到二维数组中
        }
    
        memset(f, 0, sizeof f);
        f[0][0] = 1; //因为没有第-1列,所以也就不可能横着到第0列,那么就是只有竖着放这1种状态
        for(int i = 1; i<= m; i ++) //遍历每一列
            for(int j = 0; j < (1 << n); j++) //遍历第i列的所有状态
                for(auto k:state[j]) //遍历第i-1列的所有状态,如果有符合合法状态的k,就执行状态转移方程
                    f[i][j] += f[i-1][k]; //将第i列j状态的方案数与前一列k状态的方案数累加
                
        cout<<f[m][0]<<endl; //意味最后一列不再有横着的长方形伸出去,即所有方案数都已处理完
    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值