动态规划是一种top-down求解模式,关键在于分解和求解子问题,然后根据子问题的解不断向上递推,得出最终解

因此dp涉及到保存每个计算过的子问题的解,这样当遇到同样的子问题时就不用继续向下求解而直接可以得到结果。状态压缩就是用来保存子问题的解的,主要思想是把所有可能的状态(子问题)用一个数据结构(通常是整数)统一表示,再用map把每个状态和对应结果关联起来,这样每次求解子问题时先find一下,如果map里面已经有该状态的解就不用再求了;同样每次求解完一个状态的解后也要将其放入map中保存

状态压缩适用于二元状态,即每一列的取值只有0和1,且不适合求解规模很大的问题(否则状态太多根本保存不了)

LeetCode #464  Can I Win

https://leetcode.com/problems/can-i-win/

In the "100 game," two players take turns adding, to a running total, any integer from 1..10. The player who first causes the running total to reach or exceed 100 wins.

What if we change the game so that players cannot re-use integers?

For example, two players might take turns drawing from a common pool of numbers of 1..15 without replacement until they reach a total >= 100.

Given an integer maxChoosableInteger and another integer desiredTotal, determine if the first player to move can force a win, assuming both players play optimally.

You can always assume that maxChoosableInteger will not be larger than 20 and desiredTotal will not be larger than 300.

一开始看到这题,我想这不就是博弈树嘛!然后也没多思考,就开始啪啦啪啦地敲miniMax,再搞个αβ剪枝感觉时间复杂度也差不多了。一提交结果TL,整个人都不好了,还以为自己剪枝没剪好,后来实在怂了看了discuss才意识到要用深搜,像博弈树那样等暴力广搜完早就超时了=  =、(附上我超时的博弈树代码)

int minMaxTree( bool turn, set<int> &cho, int goal, int now, int a, int b )
{
if(now>=goal)
{
if(turn)
return -1;
else return 1;
}
set<int>::iterator itr;
if(turn)//max方
{
for(itr=cho.begin();itr!=cho.end();itr++)
{
set<int> tmp = cho;
tmp.erase(tmp.find(*itr));
a = minMaxTree(0,tmp,goal,now+*itr,a,b);
if(a>=b)
break;
}
}
else
{
for(itr=cho.begin();itr!=cho.end();itr++)
{
set<int> tmp = cho;
tmp.erase(tmp.find(*itr));
b = minMaxTree(1,tmp,goal,now+*itr,a,b);
if(a>=b)
break;
}
}
if(turn) return a;
else return b;
}

之后我便参考discuss上的代码实现了dp+状态压缩(基本上是照着打的...),思路是暴力深搜,用状态压缩记录重复的状态降低时间消耗。这里主要说一下状态的表示方法,该问题每个状态(子问题)之间的不同之处在于:

1. 可以选用的数

2. 当前目标数(当前离原始目标还差多少)

即当两个状态上述两项都相同时,则视为同一个状态。状态区分不需要考虑当前是哪一方,因为双方实质一样即都想赢

现在考虑如何表示状态,可选数上限为N,那么每个状态就会有N个布尔值表示对应的数是否已被使用,比如N=3,[true false true]表示1、3可以用,2已被用。用01表示的话就是101,可以发现其能够转换成对应的2进制数,因此用N位的2进制整数就可以直接表示每次的可选数情况

解决了如何表示可选用数,还需要表示当前目标,方法是直接把map放到vector里,用vector下标表示目标数,比如vector[goal][nums] = true,表示当目标数为goal,当前状态为nums时,玩家可以赢该游戏

bool miniMax( int status, vector<unordered_map<int,bool> > &dp, int goal, int maxn )
{
if(dp[goal-1].find(status)!=dp[goal-1].end())//该状态已经被搜索过
return dp[goal-1][status];
for( int i=maxn-1; i>=0; i-- )
{
if(status & (1<<i))//遍历每个数字,如果该数字还没被使用
{
//亦或,把该位变0表示使用该数字
if( i+1 >= goal || !miniMax(status^(1<<i),dp,goal-i-1,maxn) ) //如果当前已经能实现目标,或对方接下来不能赢
{
dp[goal-1][status] = true;
return true;
}
}
}
dp[goal-1][status] = false;
return false;
} bool canIWin(int maxChoosableInteger, int desiredTotal)
{
if(maxChoosableInteger>=desiredTotal)
return true;
if((maxChoosableInteger)*(maxChoosableInteger+1)/2<desiredTotal)//可选数之和小于目标则必定不可能成功
return false;
int status = (1 << maxChoosableInteger) - 1;//初始状态为全1即全部数字都可用
vector<unordered_map<int,bool> > dp(desiredTotal);//记录状态,dp[goal][sta]表示当前可用数为sta,目标为goal时能不能赢
return miniMax(status,dp,desiredTotal,maxChoosableInteger);
}

这里用unordered_map代替map,搜索时速度会更快(相应空间代价更高)。有个小插曲是在miniMax中传dp时忘记传引用,导致时传参因直接copy而不停地超时,且一直找不到原因=  =、脑子不够用了

状态压缩 - LeetCode #464 Can I Win的更多相关文章

  1. [leetcode] 464. Can I Win (Medium)

    原题链接 两个人依次从1~maxNum中选取数字(不可重复选取同一个),累和.当一方选取数字累和后结果大于等于给定的目标数字,则此人胜利. 题目给一个maxNum和targetNum,要求判断先手能否 ...

  2. [LeetCode] 464. Can I Win 我能赢吗

    In the "100 game," two players take turns adding, to a running total, any integer from 1.. ...

  3. LeetCode 464. Can I Win

    In the "100 game," two players take turns adding, to a running total, any integer from 1.. ...

  4. leetcode 864. 获取所有钥匙的最短路径(BFS,状态压缩)

    题目链接 864. 获取所有钥匙的最短路径 题意 给定起点,要求在最短步骤内收集完所有钥匙,遇到每把锁之前只有 有对应的钥匙才能够打开 思路 BFS+状态压缩典型题目 先确定起点和总的钥匙数目,其次难 ...

  5. Number Game_状态压缩

    Description Christine and Matt are playing an exciting game they just invented: the Number Game. The ...

  6. hdu 4336 Card Collector(期望 dp 状态压缩)

    Problem Description In your childhood, people in the famous novel Water Margin, you will win an amaz ...

  7. hdu4336 Card Collector 状态压缩dp

    Card Collector Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...

  8. hdu 5724 SG+状态压缩

    Chess Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submi ...

  9. POJ-1143(状态压缩)

    Number Game Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3432 Accepted: 1399 Descripti ...

随机推荐

  1. Chloe.ORM框架应用实践

    Chloe.ORM 是国人开发的一款数据库访问组件,很是简单易用.目前支持四种主流数据库:SqlServer.MySQL.Oracle,以及Sqlite,作者为这四种数据库划分出了各自对应的组件程序集 ...

  2. .NET Framework基本概念

    http://blog.csdn.net/T573029173/article/details/41730101 .NET是微软的新一代技术平台.对技术人员来说,想真正了解什么是.NET,须先了解.N ...

  3. Ajax笔记 XHR XMLHttpRequest

    XMLHttpRequest    xhr    XmlHttpRequest var request ;    if(Windows.XMLHttpRequest)    {    request ...

  4. 真正从0开始用Unity3D制作类战地2玩法的类龙之谷、王者荣耀的手游(暨全平台游戏)

    如题,(从2017年10月18日开始)正在利用业余时间研发一款神泣Shaiya2手游,引擎用Unity3D. 原因主要有2点: 对神泣太多感情,希望能做点什么来纪念乃至留下神泣这款网游: 时机已到,是 ...

  5. Mysql数据库之auto_increment

    一.概述 在数据库应用中,我们经常需要用到自动递增的唯一编号来标识记录.在MySQL中,可通过数据列的auto_increment属性来自动生成.可在建表时可用“auto_increment=n”选项 ...

  6. DataTable数据修改,换列

    增加列             DataTable table= new DataTable();             table.Columns.Add("ID", type ...

  7. winfrom窗体加载控制台程序,可以自定义输出语句颜色

    winfrom窗体加载控制台程序,可以自定方输出语句颜色,如下图所示 怎么实现的此功能,网上有大把的方法,我这里已经把方法打包成了一个类,只需要引用调用就可以使用了,写的比较粗糙,如有发现需要改进的地 ...

  8. HDU 6092 Rikka with Subset

    Rikka with Subset Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others ...

  9. Go Deeper

    Go Deeper Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Total Sub ...

  10. 脑残手贱:被NFS祸害的调度系统

    建议:任何时候,都要三思而后行!!! 事请的缘由 系统中采用slurm调度系统来进行并行计算.但是在GPU节点上,无论如何都无法启动slurmd,报插件初始化错误的故障. 因此需要编译新的munge和 ...