Description - 问题描述

x、y、z三个容器,其最大容量分别是xMAX升、yMAX升、zMAX升,这里规定100>xMAX>yMAX>zMAX。一开始x是装满了水的,现在要用这三个没有刻度的容器量出n升水来,请打印出最少的量取步骤。
Input - 输入数据

输入只有一行,即4个整数:

xMax  yMax  zMax n

其中100>xMax>yMax>zMax,n<100

Output - 输出数据

对于每个测试数据,输出其倒水步骤,如果步骤只有一步,step不加s请参考Sample Output,无解的话输出“No Solution”

算法分析

  首先,我们需要考虑倒出者和接收者的可能分别为x->y,x->z(前提是x>0);y->x,y->z(前提是y>0);z->x,z->y(前提是z>0)共6种可能,接下来我们便对其进行详细分析。

①x->y时

前提:if(x>0),接着,我们要考虑的就是当将x中所有水倒入y中,是否已经超过了y的容量,代码如下

  1. if(x>)
  2. {
  3. if(x+y>ymax)
  4. {
  5. x=x-(ymax-y);//将x中所有水倒入y中时已超出y的最大容量
  6. y=ymax;
  7. }
  8. else
  9. {
  10. y=x+y;//将x中所有水倒入y中后未超过其最大容量
  11. x=;//即x中的水全部倒入y中,x变为空
  12. }
  13. }

②x->z时

前提:if(x>0)这时,我们可以将x->z的过程类比成x->y的过程,其实际上的过程是一样的,代码如下:

  1. if(x>)
  2. {
  3. if(x+z>zmax)
  4. {
  5. x=x-(zmax-z);//z容器倒满
  6. z=zmax;
  7. }
  8. else
  9. {
  10. z=z+x;//x倒空
  11. x=;
  12. }
  13. }

③y->x时

前提:if(y>0),这是,我们还要不要像以前那样考虑y会不会倒空,已及x会不会倒满呢?其答案是否定的,为什么呢?你可以设想:在整个装置(包括x,y以及z)中,其总水量之和为xmax。故不管y如何倒都不会使x>xmax,代码如下:

  1. if(y>)
  2. {
  3. x=x+y;//y倒空
  4. y=;
  5. }

④y->z时,同x->y

⑤z->x时,同y->x

⑥z->y,这里也是同y->x吗?其实不然,当y->x时,无论如何都不会使x>xmax。但当z->y时却有可能使y>ymax,很多人都跳入了这个坑里,导致最终程序不能ac。经过仔细思考之后,其实可以知道这是和x->y一样的。代码如下:

  1. if(z>)
  2. {
  3. if(z+y>ymax)
  4. {
  5. z=z-(ymax-y);
  6. y=ymax;
  7. }
  8. else
  9. {
  10. y=y+z;
  11. z=;
  12. }
  13. }

  在对倒水的过程进行详细分析之后,最重要的便是算法的构造。仔细分析可以知道,这道题是要求我们分析倒水的每种情况,然后找出最优解。毫无疑问,这便是bfs的思路。可能有些人不了解或对bfs接触较少,这里给上百度对其的解释以助理解:

BFS一般指宽度优先搜索,宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。而在这个问题中,我们使用队列(queue)来实现之。

  首先,假设我们已经定义了一个队列q,如下所示

  1. struct node
  2. {
  3. int qx,qy,qz;
  4. int pre;
  5. } q[maxn];

其中qx,qy,qz表示当前各个装置中水的含量,而对于pre,相信大多数人都会纳闷:这是用来干什么的呢?其实,当你仔细阅读题目便可知道,它不仅要求输出最少次数,还得打印出每倒一次水的步骤,这里的pre便是用以存储当前状态所对应的上一个状态,到最后输出时,只需沿着pre进行输出即可。

每倒一次水,就想当于产生了一个新的状态,于是就要判断这个状态是否已达到目标。因此每倒一次水,就要调用一次push过程,将新状态入队,然后判断队尾元素是否已是解,push的代码如下:
  1. void push()
  2. {
  3. bool can_push=true;
  4. for(int i=;i<=rear;i++)
  5. {
  6. if(q[i].qx==x&&q[i].qy==y&&q[i].qz==z)
  7. {
  8. can_push=false;
  9. return;
  10. }
  11. }
  12. if(can_push)
  13. {
  14. rear++;
  15. q[rear].qx=x;
  16. q[rear].qy=y;
  17. q[rear].qz=z;
  18. q[rear].pre=front;
  19. }
  20. }

同时,在倒水时如果先前已经出现了与当前状态一致的状态,那还要不要继续倒呢?显然,答案是否定的,而这里的bool型变量can_push正是帮助完成了这一点.

在倒完水后,我们还需判断是否达到了目标状态,如果达到了就跳出循环,然后输出过程,代码如下:

  1. void is_target()
  2. {
  3. if(q[rear].qx==n||q[rear].qy==n||q[rear].qz==n)
  4. flag=true;
  5. else
  6. flag=false;
  7. }

细心的人可能会发现:在模拟倒水过程时的x,y,z三个变量是哪来的呢?以是,我们还得写个取队首元素的函数,代码如下;

  1. void initial()
  2. {
  3. x=q[front].qx;
  4. y=q[front].qy;
  5. z=q[front].qz;
  6.  
  7. }

最后,我们再将刚刚模拟过的6种情况写下,再加一个while循环便可完成任务。接下来还有一点值得思考:这里的while循环的条件是什么。还记得题目中给出的“No Solution”吗。是的,在最后跳出循环后我们还得加一条is_target()(其实在跳出循环之前已经执行了一次is_target(),故只需判断flag即可)以判断是否有解。经过这样的分析后,while执行的条件便是队列是否为空is_empty()。倒水总过程的代码如下:

  1. void pour()
  2. {
  3. while(front <= rear)
  4. {
  5. initial();
  6. //x->y
  7. if(x>)
  8. {
  9. if(x+y>ymax)
  10. {
  11. x=x-(ymax-y);
  12. y=ymax;
  13. }
  14. else
  15. {
  16. y=x+y;
  17. x=;
  18. }
  19. }
  20. push();
  21. is_target();
  22. if(flag)
  23. break;
  24. //x->z
  25. initial();
  26. if(x>)
  27. {
  28. if(x+z>zmax)
  29. {
  30. x=x-(zmax-z);
  31. z=zmax;
  32. }
  33. else
  34. {
  35. z=z+x;
  36. x=;
  37. }
  38. }
  39. push();
  40. is_target();
  41. if(flag)
  42. break;
  43. //y->x
  44. initial();
  45. if(y>)
  46. {
  47. x=x+y;
  48. y=;
  49. }
  50. push();
  51. is_target();
  52. if(flag)
  53. break;
  54. //y->z
  55. initial();
  56. if(y>)
  57. {
  58. if(y+z>zmax)
  59. {
  60. y=y-(zmax-z);
  61. z=zmax;
  62. }
  63. else
  64. {
  65. z=y+z;
  66. y=;
  67. }
  68. }
  69. push();
  70. is_target();
  71. if(flag)
  72. break;
  73. //z->x
  74. initial();
  75. if(z>)
  76. {
  77. x=x+z;
  78. z=;
  79. }
  80. push();
  81. is_target();
  82. if(flag)
  83. break;
  84. //z->y
  85. initial();
  86. if(z>)
  87. {
  88. if(z+y>ymax)
  89. {
  90. z=z-(ymax-y);
  91. y=ymax;
  92. }
  93. else
  94. {
  95. y=y+z;
  96. z=;
  97. }
  98. }
  99. push();
  100. is_target();
  101. if(flag)
  102. break;
  103. front++;
  104. }
  105. }

还有一点要补充的就是这题也可以用STL里的queue来做,这样便可省去自己写队列的麻烦。

最后,给上OJ上ac的代码,仅供参考:

  1. /*
  2. Name: lwq
  3. Copyright:
  4. Author:
  5. Date: 07/12/14 00:57
  6. Description: 1777
  7. */
  8.  
  9. #include<iostream>
  10. using namespace std;
  11. const int maxn=+;
  12. int front,rear;
  13. int xmax,ymax,zmax;
  14. int n;
  15. int x,y,z;
  16. int step=;
  17. int k=;
  18. bool flag=false;
  19. struct node
  20. {
  21. int qx,qy,qz;
  22. int pre;
  23. } q[maxn];
  24. struct node_data
  25. {
  26. int x,y,z;
  27. }data[maxn];
  28. void is_target()
  29. {
  30. if(q[rear].qx==n||q[rear].qy==n||q[rear].qz==n)
  31. flag=true;
  32. else
  33. flag=false;
  34. }
  35. void initial()
  36. {
  37. x=q[front].qx;
  38. y=q[front].qy;
  39. z=q[front].qz;
  40.  
  41. }
  42. void push()
  43. {
  44. bool can_push=true;
  45. for(int i=;i<=rear;i++)
  46. {
  47. if(q[i].qx==x&&q[i].qy==y&&q[i].qz==z)
  48. {
  49. can_push=false;
  50. return;
  51. }
  52. }
  53. if(can_push)
  54. {
  55. rear++;
  56. q[rear].qx=x;
  57. q[rear].qy=y;
  58. q[rear].qz=z;
  59. q[rear].pre=front;
  60. }
  61. }
  62. void pour()
  63. {
  64. while(front <= rear)
  65. {
  66. initial();
  67. //x->y
  68. if(x>)
  69. {
  70. if(x+y>ymax)
  71. {
  72. x=x-(ymax-y);
  73. y=ymax;
  74. }
  75. else
  76. {
  77. y=x+y;
  78. x=;
  79. }
  80. }
  81. push();
  82. is_target();
  83. if(flag)
  84. break;
  85. //x->z
  86. initial();
  87. if(x>)
  88. {
  89. if(x+z>zmax)
  90. {
  91. x=x-(zmax-z);
  92. z=zmax;
  93. }
  94. else
  95. {
  96. z=z+x;
  97. x=;
  98. }
  99. }
  100. push();
  101. is_target();
  102. if(flag)
  103. break;
  104. //y->x
  105. initial();
  106. if(y>)
  107. {
  108. x=x+y;
  109. y=;
  110. }
  111. push();
  112. is_target();
  113. if(flag)
  114. break;
  115. //y->z
  116. initial();
  117. if(y>)
  118. {
  119. if(y+z>zmax)
  120. {
  121. y=y-(zmax-z);
  122. z=zmax;
  123. }
  124. else
  125. {
  126. z=y+z;
  127. y=;
  128. }
  129. }
  130. push();
  131. is_target();
  132. if(flag)
  133. break;
  134. //z->x
  135. initial();
  136. if(z>)
  137. {
  138. x=x+z;
  139. z=;
  140. }
  141. push();
  142. is_target();
  143. if(flag)
  144. break;
  145. //z->y
  146. initial();
  147. if(z>)
  148. {
  149. if(z+y>ymax)
  150. {
  151. z=z-(ymax-y);
  152. y=ymax;
  153. }
  154. else
  155. {
  156. y=y+z;
  157. z=;
  158. }
  159. }
  160. push();
  161. is_target();
  162. if(flag)
  163. break;
  164. front++;
  165. }
  166. }
  167. int main()
  168. {
  169. front=,rear=;
  170. cin>>xmax>>ymax>>zmax;
  171. cin>>n;
  172. q[front].qx=xmax;
  173. q[front].qy=;
  174. q[front].qz=;
  175. pour();
  176.  
  177. if(flag)
  178. {
  179.  
  180. while(q[rear].pre>)
  181. {
  182. k++;
  183. data[k].x=q[rear].qx;
  184. data[k].y=q[rear].qy;
  185. data[k].z=q[rear].qz;
  186. rear=q[rear].pre;
  187. }
  188. if(k>)
  189. cout<<k<<' '<<"steps"<<endl;
  190. else
  191. cout<<k<<' '<<"step"<<endl;
  192. cout<<"step0:"<<' '<<xmax<<' '<<''<<' '<<''<<endl;
  193. for(int i=k;i>=;i--)
  194. {
  195. cout<<"step"<<k-i+<<':'<<' '<<data[i].x<<' '<<data[i].y<<' '<<data[i].z<<endl;
  196. }
  197. }
  198. else
  199. {
  200. cout<<"No Solution"<<endl;
  201. }
  202. return ;
  203. }

最后,还要感谢YYL大神的不啬指教以及xaero的ppt。

这是我的第一篇博文,欢迎各位大神的指点。

yzoi1777倒水问题的详细解法的更多相关文章

  1. yzoi2226最小步数的详细解法

    Description - 问题描述 在各种棋中,棋子的走法总是一定的,如中国象棋中马走“日”.有一位小学生就想如果马能有两种走法将增加其趣味性,因此,他规定马既能按“日”走,也能如象一样走“田”字. ...

  2. 蓝桥杯(Java方法、详细解法分析)基础练习 阶乘计算

    问题描述 给定n和len,输出n!末尾len位. 输入格式 一行两个正整数n和len. 输出格式 一行一个字符串,表示答案.长度不足用前置零补全. 样例输入 6 5 样例输出 00720 数据规模和约 ...

  3. yzoi2223集合构造的详细解法

    Description - 问题描述 集合M的定义如下: 1是M中的元素 如果x是M中的元素,那么2x+1和4x+5都是M中的元素 那么,集合M中,最小的n个数是哪些? Input - 输入数据 一个 ...

  4. IOS 关于property的详细解法

    1.格式 @property (参数1,参数2,...) 类型 名字; eg: @property(nonatomic,retain) UIWindow *window; 其中参数主要分为三类: • ...

  5. 已知有两个水杯,一个11L一个7L,水可以任意使用,求怎么得到2L 的详细解法

    问题:有两个水杯,一个是11L一个是7L,水可以随便用,怎么得到2L 1.了解问题的本质 问题中给出了两个杯子,只有这两个杯子有量度,所以只能让杯中的水满进满出才能确定杯子中最后有多少水. 现在问题要 ...

  6. LeetCode 861翻转矩阵后得分详细解法

    1. 题目内容 有一个二维矩阵 A 其中每个元素的值为 0 或 1 . 移动是指选择任一行或列,并转换该行或列中的每一个值:将所有 0 都更改为 1,将所有 1 都更改为 0. 在做出任意次数的移动后 ...

  7. Bug #19528825 "UNABLE TO PURGE A RECORD"

    概述: 在生产环境中,当开启insert buffer时(参数innodb_change_buffering=all),部分实例偶尔会出现“UNABLE TO PURGE A RECORD”错误.这个 ...

  8. tyvj P1716 - 上帝造题的七分钟 二维树状数组区间查询及修改 二维线段树

    P1716 - 上帝造题的七分钟 From Riatre    Normal (OI)总时限:50s    内存限制:128MB    代码长度限制:64KB 背景 Background 裸体就意味着 ...

  9. UVA 10911 Forming Quiz Teams(dp + 集合最优配对问题)

    4th IIUC Inter-University Programming Contest, 2005 G Forming Quiz Teams Input: standard input Outpu ...

随机推荐

  1. 多台Mac电脑使用一个apple开发者账号

    直接从已安装好的机器上导出私有密钥的,具体方法如下: Xcode的organizer的IPHONE DEVELOPMENT --->Developer Profile里自带的Export和Imp ...

  2. HDOJ 2212 DFS

    Problem Description A DFS(digital factorial sum) number is found by summing the factorial of every d ...

  3. SWMM代码移植到64位平台

    在32位平台上运行SWMM模型,当节点数量到达60万以上的时候,模型运行占用内存接近1.85G的时候就会因为内存不够而无法计算.这种情况还是单独运行SWMM.exe的时候出现,如果采用SWMM.DLL ...

  4. C# 操作配置文件

    1.配置文件app.config的书写: string mdbPath = ConfigurationManager.AppSettings["mdbPath"].ToString ...

  5. 【matlab】matalb生成dll给Cpp用

    http://blog.csdn.net/scudz/article/details/13628917 这篇文章写得很好,我跟据这个,操作了一下,发现,好用,精简版总结如下 1. matlab打开一个 ...

  6. thymeleaf 和其它标签组合 获取数据

    thymeleaf 有很多的内置标签, 但是我们在开发中会引入其它很多标签, 这个时候, 后台数据过来了,前端 页面要怎么显示呢? 网上资料真的很少. 不过还是找到了答案:  th:attr 这个标签 ...

  7. Ruby学习笔记(二)

    1.block 代码块 do...end 或 {} 构成一个代码块,就像常见的 .each后面跟的代码块. my_nums = [1,2,3] my_double_nums = my_nums.col ...

  8. LeetCode201 Bitwise AND of Numbers Range Java 题解

    题目: Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all num ...

  9. zedboard--交叉编译Opencv库的生成 分类: shell ubuntu fool_tree的笔记本 ZedBoard OpenCV 2014-11-08 18:57 171人阅读 评论(0) 收藏

    Opencv的移植,xzyfeixiang和rainysky的博客. 第一步肯定是下载opencv的源码包 第二步已经做好的交叉编译环境. 第三步下载安装cmake   apt-get install ...

  10. mysql 时区 , 夏令时,冬令时

    mysql默认时区: mysql> show variables like '%time_zone%'; +------------------+--------+ | Variable_nam ...