动态规划是一种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. JavaScriptOOP

    1. OOP基础 1.1面向对象编程OOP 1.语言的分类:      ① 面向机器:汇编语言      ② 面向过程:C语言      ③ 面向对象:C++ Java PHP 等   2.面向过程与 ...

  2. java抽象类、抽象方法、接口、实现接口详解

    对于java中的抽象类,抽象方法,接口,实现接口等具体的概念就不在这里详细的说明了,网上书本都有很多解释,主要是我懒,下面通过一个例子来说明其中的精髓要点,能不能练成绝世武功,踏上封王之路,就看自己的 ...

  3. JIRA-6.3.6安装与破解

    首先下载JIRA-6.3.6的安装包: wget http://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-6.3. ...

  4. Android View, Window,Activity概念区分(2)

    (1)View:最基本的UI组件,表示屏幕上的一个矩形区域. (2)Window: 表示一个窗口,不一定有屏幕那么大,可以很大也可以很小:它包含一个View tree和窗口的layout 参数.Vie ...

  5. Android Annotations(1)

    特性:   Android Annotations是一个开源的框架,用于加速 Android应用的开发,可以让你把重点放在功能的实现上,简化了代码,提升了可维护性. 特性: 依赖注入: inject ...

  6. MySql入门(1)

    MySql入门(1) 安装 检查系统中是否已经安装了MySQL sudo netstat -tap | grep mysql 若没有显示已安装结果,则没有安装.否则表示已经安装. sudo apt-g ...

  7. C# MessageBox.Show每隔3秒自动关闭

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  8. asp.net C# 实现微信服务器配置

    微信服务器配置接收页面示例代码 /// <summary> /// 微信的Token /// </summary> const string Token = "Tok ...

  9. Locust性能测试框架,从入门到精通

    1. Locust简介 Locust是使用Python语言编写实现的开源性能测试工具,简洁.轻量.高效,并发机制基于gevent协程,可以实现单机模拟生成较高的并发压力. 主要特点如下: 使用普通的P ...

  10. linux 投影仪

    注:文章转自http://goo.gl/aI9Ycd如果侵权,请原作者留言,立即删除 之前在 R219 做 C++ 演講的時候,發現 Ubuntu 沒有辦法使用 VGA 輸出,臨時改用 Windows ...