DP总结(三) 状态压缩DP

三、状态压缩DP

3.1. 基本概念

​ 状态压缩动态规划,就是我们俗称的状压DP,是利用计算机二进制的性质来描述状态的一种DP方式很多棋盘问题都运用到了状压,同时,状压也很经常和记忆化搜索连用。一般用状态压缩的DP作为记忆化数组。

3.2. 关键操作

1 << (i - 1)			// 左移 i - 1 位.
1 << (i - 1) | state	// 加入集合中第 i 个元素.
1 << (i - 1) & state	// 判断集合中第 i 个元素是否包含在此子集中.

// 例子
state = 010001, cur = 1 << 2 = 100
state | cur = 010001 | 000100 = 010101	// 将第 i 个元素加入此时的状态.

// 例子
state = 010001, cur = 1 << 4 = 10000 
state & cur = 010001 & 010000 = 010000	// state & cur == 0 证明此时状态中不含该元素.
123456789
二进制表示100100011
是否使用

对于上述表格展示的是对于一个长度为9的数组,取 1,4,8,9 四个元素作为子集的时候,状态应该表示为 100100011.

3.3. 经典例题

LeetCode 464 我能赢吗

class Solution {
public:
    bool memo_dfs(int maxChoosableInteger, int desiredTotal, vector<int>& memo, int state){
        // 之前已经遍历过了,直接返回之前的结果。记忆化搜索中很重要的剪枝步骤.
        if(memo[state] != 2){
            return memo[state];
        }
		
        // 遍历[1, maxchoose],选择当前步加入的数字.
        for(int i = 1; i <= maxChoosableInteger; i++){
            int add_state = 1 << (i - 1);
            
            // 当前元素已经加入之前的状态中了,不需要继续加入.
            if((add_state & state) != 0){
                continue;
            }
			
            // 此时如果加入当前元素已经满足取胜条件,则return true.
            // 或者 下一手中无法满足取胜条件, 此时依然当前手胜利.
            if(desiredTotal - i <= 0 || !memo_dfs(maxChoosableInteger, desiredTotal - i, memo, state | add_state)){
                memo[state] = 1;
                return memo[state];
            }
        }
        
        // 如果遍历所有状态之后依然没有一个状态可以取胜,返回false.
        memo[state] = 0;
        
        return memo[state];
    }
    
    bool canIWin(int maxChoosableInteger, int desiredTotal) {
        vector<int> dp(1 << `maxChoosableInteger`, 2);
		
        // 小剪枝,所有元素的和一半无法到达目标(求和公式),则返回false.
        if((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal){
            return false;
        }
		
        // 记忆化搜索, 状态压缩数组主要是充当记忆化数组。
        int res = memo_dfs(maxChoosableInteger, desiredTotal, dp, 0);
		
        if(res == 1){
            return true;
        }

        return false;
    }
};

// 注意:
// 1. 虽然是先后手,但是只在一个状态下面遍历,第一个DFS当成先手选数,第二个DFS当成后手选数。
// 最后返回的结果是第一个DFS的返回结果。
// 2. 取胜条件,如果当前加入第 i 个元素之后达到取胜条件,当前手胜利,返回1,则上一手失败.
// 或者返回的下一手无法找到取胜方式,那么当前手胜利.

LeetCode 526 优美的排列

class Solution {
public:
    int memo_dfs(vector<int>& dp, int N, int position_index, int state){
        if(position_index == N + 1){
            return 1;
        }
		
        // 灵魂剪枝, 根据之前计算出的次数直接返回.
        // 对于0000 => 0010 => 0011, 0000 => 0001 => 0011
        // 上述两个状态从0011 => 1111 的过程是一样的,所以只需要计算一次过程即可.
        if(dp[state] != -1){
            return dp[state];
        }

        int res = 0;
        for(int i = 1; i <= N; i++){
            int add_state = 1 << (i - 1);
			
            // 当前状态中含有第 i 个要素.跳过.
            if((add_state & state) != 0){
                continue;
            }
			
            // 满足当前要求的 position_index 与 i 整除.
            if(position_index % i == 0 || i % position_index == 0){
                res += memo_dfs(dp, N, position_index + 1, state | add_state);
            }
        }
		
        // 得到当前状态下到达 0011 => 1111 的可满足方案.存储到dp数组中.
        dp[state] = res;

        return dp[state];
    }

    int countArrangement(int N) {
        vector<int> dp(1 << N, -1);

        return memo_dfs(dp, N, 1, 0);
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值