目录
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就讲到这里了哈,如果本文有什么错误的地方欢迎大家前来指正呀。