前几天还在踟蹰我应该注重培养做项目的能力还是修炼算法以及数据结构,然后发现这个场景有点似曾相识。曾几何时的一个月里,我有三件比较重要的事情要解决,在那个月刚开始的时候我一直在想我应该从那件事情开始着手,甚至在脑海里给这三件事情的重要性排个序,思绪争执不下,烦躁之极,不如玩玩游戏散散心吧,或许等下就知道怎么做了......结果一个月下来,没有一件事的结果能使我十分满意。做项目的编程能力固然需要学习,但技术更替这么快,亘古不变的是那些算法的思维和设计的数据结构,既然决定要学,就踏踏实实从算法方面入手吧。

  前些日子了解了滚动数组的求解,这几天学习了计划搜索。什么是记忆搜索,就是你在当前step所做的决定影响到了你接下来的每个决定,你必须考虑下一步的结果,使最后的价值最大化。以下通过两个例子来详细分析一下。(鄙人觉得,学习算法的初步就是学习例题,刚开始的时候不要急于从例题当中提取那些比较深度的思维,只管学习,当火候到了,自然就举一反三,当然这其中的兴趣成分十分重要。就像那个谁名人来的说过:我读诗,并不刻意去理解这句诗是什么意思,我只是喜欢她,我只是不断去读她,直到有一天,我们相互理解)

1.Coins in a line : http://www.lintcode.com/en/problem/coins-in-a-line/

2.Coins in a line II : http://www.lintcode.com/en/problem/coins-in-a-line-ii/

对于第一题,大概题意是这样的:给你一排硬币,两个足够聪明的人交替从头开始取这些硬币(两个人必须足够聪明,假设是爱因斯坦和特斯拉吧^ ^),每次可以取一或两个,当取走最后哪个硬币的人获胜,给你n个硬币,问先手取的人能否获胜。

按照解动态规划的步骤来

  1、状态(State):当目前有n个硬币,我要怎么取才能保证我能取得最后一枚硬币,我们往前推一步,n个硬币当中,我可以取一枚或者两枚,当你这一步取完的时候,对手也会执行同样的动作(足够聪明的对手也会考虑怎么取才能取到最后一枚),我们用布尔类型dp[x]来表示当目前剩下x枚硬币且轮到你来取硬币的时候能不能赢。这个时候我们分两种情况讨论:

①我取一枚,对手可以取一枚或者两枚,所以到下一轮到我取硬币的时候,标示你能否获胜的状态就是dp[x-2]或dp[x-3](对手取一枚或者两枚)。

②我取两枚,对手可以取一枚或者两枚,到下一轮到我取硬币的时候,标志你能否获胜的状态就是dp[x-3]或dp[x-4]。

那也就是说,如果dp[x-2]和dp[x-3]都是true(获胜)的话,那我在x这一步肯定取一枚,这样就能保证在dp[x]的时候为true。为什么dp[x-2]和dp[x-3]要同时为true才行,因为你取一枚之后,对手取完再轮到你的时候决定你是取dp[x-2]和dp[x-3]的情况是由对手决定的,如果其中有一个为false,那对手肯定不会让另一个true发生。反之也成立,如果dp[x-3]和dp[x-4]同时为true的话,那我在x这一步的时候肯定取两枚,这样对手就无论取一枚或者两枚都无法阻止我获胜啦嘿。

                            

如示意图中,假设当前剩下四枚硬币,如果我取一枚,接下来对手面对三枚,怎么取都能让下一步的我取到最后一枚硬币,但如果我取两枚,那对手足够聪明,肯定会两枚都取而获胜,所以我在面对四枚硬币的时候肯定取一枚。此处就相当于确定,如果是我面对四枚硬币,那我肯定能赢,所以往上推,我只需要考虑能不能构造剩下四枚硬币的结果就可以了。

  2、方程(Function):状态确定了,方程也随之能够确定下来。不难得出,当剩下n枚硬币,此时先手的人能否获胜为dp[n], 有

  dp[n] = MemorySearch(n-) && MemorySearch(n-) || MemorySearch(n-) && MemorySearch(n-);

  3、初态(Initialization):轮到我取硬币,如果已经没有了(n=0),那说明对手赢了,如果剩下一枚或者两枚,那我能赢,剩下三枚,对手赢,剩下四枚,我赢。因此有

   if( == n || == n) dp[n] = false; if( == n|| == n|| == n) dp[n] =true

  4、结果(Result): dp[n] (面对n枚硬币先手)

 代码实现如下:传入n枚硬币作为参数,返回是否能够获得胜利。

bool firstWillWin(int n) {
int dp[n+];
bool flag[n+] = {false};
return MemorySearch(dp,n,flag);
}
bool MemorySearch(int *dp,int n,bool *flag){
if(true == flag[n])
return dp[n];
flag[n] = true;
if( == n|| == n) dp[n] = false;
else if( == n||==n||==n||==n) dp[n]= true;
else{
dp[n] = ( MemorySearch(dp,n-,flag) && MemorySearch(dp,n-,flag) )|| ( MemorySearch(dp,n-,flag)&&MemorySearch(dp,n-,flag) );
}
return dp[n];
}

以上是根据普通记忆搜索算法的思路简单明了容易理解,但此题还可以用数组状态推移的方法解。确定初态之后,以后的每个f[x]的状态都可以由f[x-2],f[x-3],f[x-4]得出,因此有

bool firstWillWin(int n) {
bool f[n];
if( == n ||==n|| == n|| == n) return true;
if( == n || ==n) return false;
f[] = true;
f[] = true;
f[] = false;
f[] = true;
f[] = true;
for(int i=;i<n;i++)
f[i] = (f[i-] && f[i-]) || (f[i-] && f[i-]);
return f[n-];
}

此处f[x]表示面对的硬币数为x+1枚。最后的结果为f[n-1](即n枚硬币的情况)。

到此,解决了给你一堆硬币轮流取,到底会不会赢的情况。接下来考虑一下一道拓展的题目,看第二个问题:Coins in a line II

题意大概是:给你一堆不同价值的硬币,取硬币的规则跟前者一样,取完硬币的时候获得价值总值高的一方胜利,问先手取的人能否获胜。

在这种情况下,就不是取到最后一个硬币就能够赢得,每一步我取一枚或者两枚都好,只要硬币取完拥有的价值量最高。那好,当硬币少于或者等于3枚的时候先手,那没有疑问,取最大值价值量最高(1枚的时候取1枚,2枚或3枚的时候取2枚),当然这个时候轮到对手的时候也会这么取,但当有四枚硬币的时候怎么取。举个栗子,当前为【1,2,4,8】的时候,我取一个,价值为【1】,剩下【2,4,8】,经过前面的推导,剩下三枚取两枚,结果被取走{2,4},我取最后一个【8】,这个时候价值总量为【9】。如果我取两个,价值为【3(1+2)】,剩下【4,8】,必须被对手取走,此时价值总量为【3】,比前一种情况的价值量低,所以在剩下四枚硬币而且此时我先手的话,肯定能根据这种方法来判断我到底取一枚还是两枚使最后获得的价值量最高。用dp[x]来表示当前剩下x枚硬币的时候取到最后能取得的最大价值量,当前我取一个,下一轮我面对的价值量就是f[x-2]或f[x-3],因为对手足够聪明,所以他肯定会根据f[x-2]和f[x-3]的价值量来决定当他面对x-1个硬币的时候是取一枚还是两枚,此时的话 dp[x] = min(dp[x-],dp[x-]) + values[x] ,此为面对x枚硬币取一枚的情况。第二种情况我取两枚,下一轮我面对的价值量是f[x-3]或f[x-4],同样的道理,但最后 dp[x] = min(f[x-],f[x-]) + values[x] + values[x+] ,所以我到底取一枚还是两枚,价值量是为 dp[x] = max( min(dp[x-],dp[x-])+values[x],min(dp[x-],dp[x-])+values[x]+values[x+]) ,往上推同样的道理,dp[x]的最大值取决于dp[x-2],dp[x-3],dp[x-4]的值,最后可以得出dp[n]就是面对n枚硬币的时候先手可以取得最大的价值量,此时只要判断dp[n] > sum/2即可胜出,sum为所有硬币的总共价值量。

代码如下:

bool firstWillWin(vector<int> &values) {
int sum = ;
int dp[values.size()+];
bool flag[values.size()+];
for(int i=;i<values.size()+;i++) flag[i] = false;
for(vector<int>::iterator ite = values.begin();ite!=values.end();ite++)
sum += *ite;
return sum/<MemorySearch(dp,values.size(),flag,values);
}
int MemorySearch(int* dp,int n,bool *flag,vector<int> values){
if(flag[n])
return dp[n];
flag[n] = true;
int count = values.size();
if( == n) dp[n] = ;
else if( == n) dp[n] = values[count-n];
else if( == n|| == n) dp[n] = values[count-n] + values[count-n+];
else{
dp[n] = max( min(MemorySearch(dp,n-,flag,values),MemorySearch(dp,n-,flag,values))+ values[count-n],
min(MemorySearch(dp,n-,flag,values),MemorySearch(dp,n-,flag,values))+values[count-n]+values[count-n+] );
}
return dp[n];
}

除了这个方法,还有类似第一个题目第二种解法的方法,此处留给读者自己实现。

以上为个人对记忆搜索算法的求解过程的理解,每一句代码均经过本人测试可用,如有问题,希望大家提出斧正。

尊重知识产权,转载引用请通知作者并注明出处!

【动态规划】记忆搜索(C++)的更多相关文章

  1. 记忆搜索与动态规划——DP背包问题

    题目描述 01背包问题 有n个重量和价值分别为\(w_i,v_i\)的物品.从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值中总和的最大值. 限制条件 1 <= n <= 10 ...

  2. 蓝桥杯---地宫取宝(记忆搜索=搜索+dp)

    题目网址:http://lx.lanqiao.org/problem.page?gpid=T120 问题描述 X 国王有一个地宫宝库.是 n x m 个格子的矩阵.每个格子放一件宝贝.每个宝贝贴着价值 ...

  3. 动态规划——数字三角形(递归or递推or记忆化搜索)

    动态规划的核心就是状态和状态转移方程. 对于该题,需要用抽象的方法思考,把当前的位置(i,j)看成一个状态,然后定义状态的指标函数d(i,j)为从格子出发时能得到的最大和(包括格子本身的值). 在这个 ...

  4. 动态规划系列(零)—— 动态规划(Dynamic Programming)总结

    动态规划三要素:重叠⼦问题.最优⼦结构.状态转移⽅程. 动态规划的三个需要明确的点就是「状态」「选择」和「base case」,对应着回溯算法中走过的「路径」,当前的「选择列表」和「结束条件」. 某种 ...

  5. 增强学习(三)----- MDP的动态规划解法

    上一篇我们已经说到了,增强学习的目的就是求解马尔可夫决策过程(MDP)的最优策略,使其在任意初始状态下,都能获得最大的Vπ值.(本文不考虑非马尔可夫环境和不完全可观测马尔可夫决策过程(POMDP)中的 ...

  6. 简单动态规划-LeetCode198

    题目:House Robber You are a professional robber planning to rob houses along a street. Each house has ...

  7. 动态规划 Dynamic Programming

    March 26, 2013 作者:Hawstein 出处:http://hawstein.com/posts/dp-novice-to-advanced.html 声明:本文采用以下协议进行授权: ...

  8. 动态规划之最长公共子序列(LCS)

    转自:http://segmentfault.com/blog/exploring/ LCS 问题描述 定义: 一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则 ...

  9. C#动态规划查找两个字符串最大子串

     //动态规划查找两个字符串最大子串         public static string lcs(string word1, string word2)         {            ...

随机推荐

  1. freemarker报错之十

    1.错误描述 <html> <head> <meta http-equiv="content-type" content="text/htm ...

  2. Error Code: 1630. FUNCTION rand.string does not exist

    1.错误描述 13:50:13 call new_procedure Error Code: 1630. FUNCTION rand.string does not exist. Check the ...

  3. java.lang.IllegalArgumentException: Document base E:\Eclipse\workspace\.metadata\.plugins\org.eclips

    1.错误描述 四月 13, 2015 5:56:55 下午 org.apache.catalina.core.AprLifecycleListener init 信息: The APR based A ...

  4. Linux查看磁盘剩余空间

    Linux查看磁盘剩余空间 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ df 文件系统 1K-blocks 已用 可用 已用% 挂载点 /dev/sda8 ...

  5. freemarker.core.ParseException:Unexpected end of file reached

    1.错误原因 freemarker.core.ParseException:Unexpected end of file reached 2.错误原因 由于在宏定义中,运用组件时没有关闭标签,导致出错 ...

  6. 项目中的导出(jxl插件)

    第一步,获取要导出的参数,为导出做准备 public ModelAndView downloadInfo(final HttpServletRequest request, final HttpSer ...

  7. 加深try catch Finnly的理解

    上代码 public String twoGetFeeInfoByWithUnit(JSONArray jsonArray,String key1,String key2){ String Debit ...

  8. I2C总线通讯协议

    I2C总线通讯协议 1. I2C总线简介 I2C是Inter-Integrated Circuit的简称,读作:I-squared-C.由飞利浦公司于1980年代提出,为了让主板.嵌入式系统或手机用以 ...

  9. 自定义JS乘法运算误差解决!

    在实际开发中遇到这样一个乘法公式:数量*单价=总价 像这样的浮点数列子:200*8.2,JS算出的结果是: 像这种浮点数的乘法计算就会有误差,我们需要得到准确的值应该是:1640,与我们后台C#计算结 ...

  10. Groovy实现原理分析——准备工作

    欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...