简单状压dp(以力扣464为例)

目录

1.状态压缩dp是啥?

2.题目分析

3.解题思路

4.算法分析

5.代码分析

6.代码一览

7.结语


1.状态压缩dp是啥?

顾名思义,状态压缩dp就是将原本会超出内存限制的存储改用更加有效的存储方式。简而言之,就是压缩dp的空间。

举个例子:假设这里有3盏灯,每盏灯的状态只有开和关。请问不改变顺序的前提下开关这些灯,有多少种情况。这里假设0是关闭,1是开启,那么等就有以下八种情况:

如果我们用字符串存储,就需要八个三长度的字符串。但是,我们可以把这些状态看作2进制的序列,那么我们使用一个数字7就可以保存这八种状态,比如数字4转化为二进制就是100,数字1就是001,这就是一种典型的状态压缩。

所以学习状压dp需要一定的位运算的基础哈。


2.题目分析

拿到题面我们要先对数据量进行分析,这里最多是有20个数据,那么比赛中所有可能的情况就是2的20次方种,我们这里是可以进行搜索的。


3.解题思路

我们看题面中给的数字个数有20个,我们就可以使用20位2进制存储排列的情况。然后我们简单的模拟一下游戏过程。假设10有个数:

  • 过程1:

        玩家1:2

        玩家2:3

        玩家1:4

        玩家2:5

        玩家1:?

  • 过程2:

        玩家1:4

        玩家2:3

        玩家1:2

        玩家2:5

        玩家1:?

我们分析一下这两个过程,我们玩家1到达“?”处时,剩余的数字和可选的数字都是一样的,所以这两个情况的结局是一样的。这里可以推理出,如果我们不做任何处理,就会产生大量的重复搜索,所以这里可以使用记忆化搜索或者dp等解题。


4.算法分析

因为本文章讲的是状压dp,所以这里我们选择使用dp进行搜索。这里有n个数字,那么会有2的n种情况,我们计算一下2的20次方个int变量没有超出内存,所以我们可以定义一个数组用来存储搜索过的情况,我们进行搜索的时候,再与这里的状态进行交互。


5.代码分析

int state=(1<<(maxChoosableInteger+1))-1;
vector<int> dp(state+1);

这里题面中的maxChoosableInteger是可以选择的数字个数,这里创建的state代表数字选择的状态,dp就是每个状态下的胜负情况,题目中是需要我们判断先手是不是必胜,所以我们只需要找到一条可以必胜的路线就可以了,如果所有路线都不能胜利,那么就是失败。

    bool canIWin(int maxChoosableInteger, int desiredTotal)
    {
        if(desiredTotal==0)
            return true;

        if(maxChoosableInteger*(maxChoosableInteger+1)/2<desiredTotal)
            return false;

        int state=(1<<(maxChoosableInteger+1))-1;
        vector<int> dp(state+1);

        return f(desiredTotal,state,dp,maxChoosableInteger);
    }

上面的这个代码中有两个if,这里是进行特殊判断的,因为如果一开始的数字就是0,就直接判断胜利,因为你不论选择什么数字都可以超过0,下面的if是判断这局的所有和是否可以达到目标值,如果所有的值相加都无法达到目标值,那么就没有胜利者,既然没有胜利者,也就没有所谓的必胜策略。

下面的f就是我们的搜索函数

    bool f(int last,int state,vector<int>& dp,int n)
    {
        if(last<=0)
            return false;

        if(dp[state]!=0)
        {
            return dp[state] == 1;
        }
        
        bool ret=false;
        for(int i=1;i<=n;i++)
        {
            if(((1<<i)&state) && !f(last-i,state^(1<<i),dp,n))
            {
                ret=true;
                break;
            }
        }

        dp[state]= ret ? 1 : -1;

        return ret;
    }

这里的last是上次选择后距离目标数的距离,state是数字选择的状态,这里的n就是可以选择的数字的最大值。这里我们采用的递归写法,在这个函数中,我们的两个玩家互相改变先手,什么意思呢?就是当我选完了一个数字,轮到另一个玩家选,那么就相当于剔除了一个数字和改变了目标值的新游戏的先手。所以,当这里递归发现last<=0了说明上一个玩家胜利了,这个玩家就失败了。

下面的if就是判断这个情况有没有被搜索过,如果dp[state]!=0说明这个情况已经被搜索过了,我们直接返回就行,这里我定义1就是胜利-1就是失败,所以这里判定如果是1就返回true。

接下来的for循环之前,我们先假设这次比赛是失败的,然后遍历每一种情况,这里首先需要判断,这个数字没有选择过,只需要判断state上的第i位是不是1就可以了,当然我们还需要判断,下一位玩家是不是失败,这里我们last减去我们选择的数字,然后更新state的状态,如果下手是失败的,那么就可以判断我们本次是成功的。所以ret变为true退出循环。

最后更新dp[state]的值,如果ret是true就赋值为1,否则赋值为-1。保存完毕后返回ret的值就完成了搜索。


6.代码一览

class Solution 
{
public:

    bool f(int last,int state,vector<int>& dp,int n)
    {
        if(last<=0)
            return false;

        if(dp[state]!=0)
        {
            return dp[state] == 1;
        }
        
        bool ret=false;
        for(int i=1;i<=n;i++)
        {
            if(((1<<i)&state) && !f(last-i,state^(1<<i),dp,n))
            {
                ret=true;
                break;
            }
        }

        dp[state]= ret ? 1 : -1;

        return ret;
    }

    bool canIWin(int maxChoosableInteger, int desiredTotal)
    {
        if(desiredTotal==0)
            return true;

        if(maxChoosableInteger*(maxChoosableInteger+1)/2<desiredTotal)
            return false;

        int state=(1<<(maxChoosableInteger+1))-1;
        vector<int> dp(state+1);

        return f(desiredTotal,state,dp,maxChoosableInteger);
    }
};

7.结语

简单状态压缩dp就讲到这里了哈,如果本文有什么错误的地方欢迎大家前来指正呀。

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值