题目

一群小孩子在玩小石子游戏,游戏有两种玩法。
(1)路边玩法
有n堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)。
(2)操场玩法
一个圆形操场周围摆放着n堆石子,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)。

问题分析

这个游戏用不同的合并方法就会有不同的成本,我们要求得成本的最值,这两种玩法的区别在于路边版是把石子排成一条直线,而操场版是把石子排成一个圆圈,那么操场版就可以在路边版的基础上求解。

算法

这部分有参考《趣学算法》,也有很多自己的理解和思考,如有bug,欢迎批评指正。(鞠躬)

动态规划

这个问题同样具有最优子结构,也就是整个问题的最优解会包含子问题的最优解,类似于编辑距离,游艇租赁问题,证明可以采用反证法,具体证明这里不再给出,与前面两类问题相似,如感兴趣可移步前两类问题。

算法核心

这个问题在对于求解子问题的部分与游艇租赁问题非常类似,也是先求出堆数少的石子合并的成本,再利用已求的子问题的最优解来求堆数较多的石子合并的成本,最后求出所有石子合并的成本,其中最重要的当然是递推公式啦。
a[]:记录每堆石子的数目;
Min[i][j]:从第i堆到第j堆合并的最小成本;
Max[i][j]:从第i堆到第j堆合并的最大成本;
k:第i堆和第j堆中间的石子堆;
r(i,j):从第i堆到第j堆的总石子数;
最小成本的递推公式:
if i== j,Min[i][j]=0;
if i!=j,Min[i][j]=min{Min[i][j],Min[i][k]+Min[k+1][j]+r(i,j)};
最小成本的递推公式:
if i== j,Min[i][j]=0;
if i!=j,Max[i][j]=max{Max[i][j],Max[i][k]+Max[k+1][j]+r(i,j)};
这个递推公式还是比较好理解的,就是在石子堆i和j之间找到一个石子堆k使得先把i到k的合并,k+1到j的合并,再把这两个石子堆合并,这样合并的成本最小。

算法流程

用a[]存储每堆的石子数目,sum[]计算到当前堆的总石子数,将Min[][]初始化为无穷大,Max[][]初始化为-1(这是求最值的技巧,最小值初始化为无穷大,最大值初始化为负,具体数值视情况而定),然后把Min[i][i]和Max[i][i]都置为0。之后按照距离大小设循环,d从1到n-1,然后距离确定的时候,从第一个堆开始每个堆都求到一定距离堆的最小和最大成本,这个就用之前的递推公式即可。举个栗子来说就是,比如距离为3,从第1个堆开始求,就是先求从第1个堆合并到第4个堆的最小和最大成本,那么k可以取1,2,3(注意不能取4,因为划分的时候第二部分是从k+1开始的,如果取4就会超出范围),之后设循环计算得到最值即可。路边版的最大和最小成本分别是Max[1][n],Min[1][n]。

操场版————路边版的升级版

之前说过路边版是把石子排成直线,而操场版是把石子排成圆圈,其实算法是一样的,只不过操场版的规模比路边版更大。路边版是n堆,操场版是2*n-1堆,就是之前n堆和路边版是一样的,之后的就是从a[1]到a[n-1],无论从a[1]到a[n]哪一堆开始向后数n个都是一个路边版,而且每一个都是操场版都是在圆圈中可以实现的,换句话说一个操场版可以看成n个路边版,那么求最值只需要在这n个路边版的最值中求即可。

代码实现

石子合并游戏(路边版&&line版)
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=105;
const int INF=1<<29;int n;
int a[maxn];//存储每堆的石子数
int Min[maxn][maxn];//记录两堆合并的最小成本
int Max[maxn][maxn];//记录两堆合并的最大成本
int sum[maxn];//记录到当前堆数的总石子数,以便计算最后两堆合并时的成本void init()
{int cnt=0;int i,j;memset(sum,0,sizeof(sum));for(i=1; i<=n; ++i){cnt+=a[i];sum[i]=cnt;}for(i=1; i<=n; ++i)for(j=i; j<=n; ++j){Min[i][j]=INF;//将最小成本初始化为无穷大Max[i][j]=-1;//将最大成本初始化为-1}
}int r(int x,int y)//计算从第x堆到第y堆合并过程中最后一次合并所需成本,也就是从x到y的总石子数
{return sum[y]-sum[x-1];
}void strstone()
{int i,j,k,d;for(i=1; i<=n; ++i) //初始化,由自己到自己成本为0{Min[i][i]=0;Max[i][i]=0;}for(d=1; d<=n-1; ++d) //按照距离计算{for(i=1; i<=n-d; ++i) //从第1堆开始{j=i+d;for(k=i; k<i+d; ++k){Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+r(i,j));Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+r(i,j));}}}
}int main()
{cout<<"请输入石子堆数:";cin>>n;cout<<"请依次输入每堆的石子数:";int i;for(i=1; i<=n; ++i)cin>>a[i];init();strstone();cout<<"石子游戏(路边版)的最小成本为:"<<Min[1][n]<<endl;cout<<"石子游戏(路边版)的最大成本为:"<<Max[1][n]<<endl;return 0;
}
石子合并游戏(操场版&&circle版)
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=105*2;
const int INF=1<<29;int n,m;//实际石子堆数,计算石子堆数
int a[maxn];//存储每堆的石子数
int Min[maxn][maxn];//记录两堆合并的最小成本
int Max[maxn][maxn];//记录两堆合并的最大成本
int sum[maxn];//记录到当前堆数的总石子数,以便计算最后两堆合并时的成本
int min_num,max_num;//最小和最大成本void init()
{int cnt=0;int i,j;memset(sum,0,sizeof(sum));for(i=1; i<=m; ++i){cnt+=a[i];sum[i]=cnt;}for(i=1; i<=m; ++i)for(j=i; j<=m; ++j){Min[i][j]=INF;//将最小成本初始化为无穷大Max[i][j]=-1;//将最大成本初始化为-1}
}int r(int x,int y)//计算从第x堆到第y堆合并过程中最后一次合并所需成本,也就是从x到y的总石子数
{return sum[y]-sum[x-1];
}void playstone()
{int i,j,k,d;for(i=1; i<=m; ++i) //初始化,由自己到自己成本为0{Min[i][i]=0;Max[i][i]=0;}for(d=1; d<=m-1; ++d) //按照距离计算{for(i=1; i<=m-d; ++i) //从第1堆开始{j=i+d;for(k=i; k<i+d; ++k){Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+r(i,j));Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+r(i,j));}}}
}void cal()
{min_num=INF;max_num=-1;for(int i=1;i<=n;++i){if(Min[i][i+n-1]<min_num)min_num=Min[i][i+n-1];if(Max[i][i+n-1]>max_num)max_num=Max[i][i+n-1];}
}int main()
{cout<<"请输入石子堆数:";cin>>n;m=2*n-1;cout<<"请依次输入每堆的石子数:";int i;for(i=1; i<=n; ++i)cin>>a[i];for(i=n+1;i<=m;++i)a[i]=a[i-n];init();//初始化playstone();cal();//计算最小成本和最大成本cout<<"石子游戏(操场版)的最小成本为:"<<min_num<<endl;cout<<"石子游戏(操场版)的最大成本为:"<<max_num<<endl;return 0;
}
石子合并游戏(综合版&&classic版)

这个版本是二者的结合,路边版的数据是可以直接从操场版中的得到的,其实操场版中就包含了路边版,所以这个版本与操场版的区别就在于主函数。

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=105*2;
const int INF=1<<29;int n,m;//实际石子堆数,计算石子堆数
int a[maxn];//存储每堆的石子数
int Min[maxn][maxn];//记录两堆合并的最小成本
int Max[maxn][maxn];//记录两堆合并的最大成本
int sum[maxn];//记录到当前堆数的总石子数,以便计算最后两堆合并时的成本
int min_num,max_num;//最小和最大成本void init()
{int cnt=0;int i,j;memset(sum,0,sizeof(sum));for(i=1; i<=m; ++i){cnt+=a[i];sum[i]=cnt;}for(i=1; i<=m; ++i)for(j=i; j<=m; ++j){Min[i][j]=INF;//将最小成本初始化为无穷大Max[i][j]=-1;//将最大成本初始化为-1}
}int r(int x,int y)//计算从第x堆到第y堆合并过程中最后一次合并所需成本,也就是从x到y的总石子数
{return sum[y]-sum[x-1];
}void playstone()
{int i,j,k,d;for(i=1; i<=m; ++i) //初始化,由自己到自己成本为0{Min[i][i]=0;Max[i][i]=0;}for(d=1; d<=m-1; ++d) //按照距离计算{for(i=1; i<=m-d; ++i) //从第1堆开始{j=i+d;for(k=i; k<i+d; ++k){Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+r(i,j));Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+r(i,j));}}}
}void cal()
{min_num=INF;max_num=-1;for(int i=1;i<=n;++i){if(Min[i][i+n-1]<min_num)min_num=Min[i][i+n-1];if(Max[i][i+n-1]>max_num)max_num=Max[i][i+n-1];}
}int main()
{cout<<"请输入石子堆数:";cin>>n;m=2*n-1;cout<<"请依次输入每堆的石子数:";int i;for(i=1; i<=n; ++i)cin>>a[i];for(i=n+1;i<=m;++i)a[i]=a[i-n];init();//初始化playstone();cout<<"石子合并游戏(路边版)的最小成本为:"<<Min[1][n]<<endl;cout<<"石子合并游戏(路边版)的最大成本为:"<<Max[1][n]<<endl;cal();//计算操场版最小成本和最大成本cout<<"石子游戏(操场版)的最小成本为:"<<min_num<<endl;cout<<"石子游戏(操场版)的最大成本为:"<<max_num<<endl;return 0;
}

【动态规划】小石子游戏-石子合并相关推荐

  1. 动态规划---石子游戏

    动态规划---石子游戏 石子游戏(leetcode877) 石子游戏(leetcode1140) 石子游戏(leetcode1686) 石子游戏(leetcode877) 题目描述 亚历克斯和李用几堆 ...

  2. NYOJ 1427-小石子游戏【石子合并】

    题目描述: 一群小孩子在玩小石子游戏,游戏有两种玩法. (1)路边玩法 有n堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量.求将这N ...

  3. 牛客网CSP-S提高组赛前集训营1题解(仓鼠的石子游戏 [博弈论] + 乃爱与城市的拥挤程度 [树上DP] + 小w的魔术扑克[dfs + 离线])

    文章目录 T1:仓鼠的石子游戏 题目 题解 代码实现 T2:乃爱与城市拥挤程度 题目 题解 代码实现 T3:小w的魔术扑克 题目 题解 代码实现 T1:仓鼠的石子游戏 题目 仓鼠和兔子被禁止玩电脑,无 ...

  4. 博弈问题-取石子(D题小牛vs小客)附取石子游戏总结

    题目: 链接:https://www.nowcoder.net/acm/contest/75/D 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6 ...

  5. leetcode——石子游戏系列题目

    石子游戏 亚历克斯和李用几堆石子在做游戏.偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] . 游戏以谁手中的石子最多来决出胜负.石子的总数是奇数,所以没有平局. 亚历克斯和李轮流进行,亚历 ...

  6. Leetcode1686. 石子游戏 VI[C++题解]:博弈论、按照a+b贪心

    文章目录 题目分析 题目链接 题目分析 博弈论:最坏情况下求最好. 按a[i]+b[i]a[i] + b[i ]a[i]+b[i] 从大到小排序 解释: (博弈论)每个人最优的选择都是 要求自己的−对 ...

  7. 百-寒-进-15-D-小石子游戏

    小石子游戏 题目描述 一群小孩子在玩小石子游戏,游戏有两种玩法. (1)路边玩法 有n堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量 ...

  8. BZOJ 1874: [BeiJing2009 WinterCamp]取石子游戏(SG函数)

    Time Limit: 5 Sec  Memory Limit: 162 MB Submit: 871  Solved: 365 [Submit][Status][Discuss] Descripti ...

  9. bzoj1874: [BeiJing2009 WinterCamp]取石子游戏

    1874: [BeiJing2009 WinterCamp]取石子游戏 Time Limit: 5 Sec  Memory Limit: 162 MB Submit: 834  Solved: 350 ...

最新文章

  1. perf+火焰图 = 性能分析利器
  2. python生成固定形状的词云图
  3. Linux C编程一站式学习读书笔记——socket编程
  4. vim-go开发环境Tagbar插件和NERTree插件安装
  5. EBS R12.2 ADOP (R12.2 AD Online Patching) - 5
  6. 最全的 pip 使用指南,50% 你可能没用过。
  7. BugkuCTF-PWN题pwn5-overflow2超详细讲解
  8. 无法启动parallels 缺少必要组件_Parallels 体验 Manjaro 19 (Xfce)
  9. Java实现可视化迷宫
  10. oracle中的存储过程
  11. mysql 远程 更改
  12. Java多线程学习笔记(三)——Future和FutureTask
  13. 判断已知顺序的三个点是顺时针还是逆时针
  14. 【字源大挪移—读书笔记】 第三部分:字尾
  15. 一键命令测试云主机的带宽
  16. Arduino录音时间延长_如何用arduino设计出可以语音播报的数字时钟
  17. C语言开发单片机为什么大多数都采用全局变量的形式?
  18. 10岁男童高考566分8岁开发操作系统
  19. excel中创建随机数(包含英文+数字随机数生成)
  20. 雅虎高管解读财报 当务之急是分拆资产

热门文章

  1. java.text.dateformat_调用static java.text.DateFormat的方法不可取?
  2. 关于使用iframe嵌套页面的跳转方式
  3. iframe嵌套页面下载问题
  4. golang处理excel打开csv乱码问题
  5. 毕业设计之 --- 基于大数据分析的股票预测系统
  6. html5常用英文单词,英语中最常用的50个单词(上)
  7. Git workflow 选型分析
  8. JavaScript中几个不常用的绑定事件
  9. 一些好的模式识别数据网站
  10. 1.7 的ConcurrentHashMap要得不