倒水问题(Fill, UVa 10603)
有装满水的6升的杯子、空的3升杯子和1升杯子,3个杯子中都没有刻度。在不使用其他 道具的情况下,是否可以量出4升的水呢?
倒水问题:一种方法是(6,0,0)→(3,3,0)→(3,2,1)→(4,2,0)
注意:由于没有刻度,用杯子x给杯子y倒水时必须一直持续到把杯子y倒满或者把杯 子x倒空,而不能中途停止。
你的任务是解决一般性的问题:设3个杯子的容量分别为a, b, c,最初只有第3个杯子装 满了c升水,其他两个杯子为空。最少需要倒多少升水才能让某一个杯子中的水有d升呢?如 果无法做到恰好d升,就让某一个杯子里的水是d’升,其中d’<d并且尽量接近d。 (1≤a,b,c,d≤200)。要求输出最少的倒水量和目标水量(d或者d’)。
分析
大致思想是通过解答树完成,利用BFS算法,假设在某一时刻,第1个杯子中有v0升水,第2个杯子中有v1升水,第3个杯子中有v2升水,称当时的系统状态为(v0,v1,v2)。这里提到了“状态”这个词,它是理解很多概念和算法的关键。简单地说,它就是“对系统当前状况的描述”。例如,在国际象棋中,当前游戏者 和棋盘上的局面就是刻画游戏进程的状态。
把“状态”想象成树中的结点,可以得到如下解答树。
注意:本题的目标是倒的水量最少,而不是步数最少。实际上,水量最少时步数不一定 最少,例如a=1, b=12, c=15, d=7,倒水量最少的方案是C->A, A->B重复7次,最后C里有7 升水。一共14步,总水量也是14。还有一种方法是C->B,然后B->A, A->C重复4次,最后C 里有7升水。一共只有10步,但总水量多达20。
因此,需要改进一下算法:不是每次取出步数最少的结点进行扩展,而是取出水量最少的结点进行扩展。这样的程序只需要把队列queue换成优先队列PriorityQueue,其他部分的代码不变。
代码
以下代码纯手工编写,欢迎指出错误和优化方法。
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.Stack;
/**
* 倒水问题(Fill, UVa 10603)
*
* 有装满水的6升的杯子、空的3升杯子和1升杯子,3个杯子中都没有刻度。在不使用其他 道具的情况下,是否可以量出4升的水呢?
*
* 注意: 由于没有刻度,用杯子x给杯子y倒水时必须一直持续到把杯子y倒满或者把杯子x倒空,而不能中途停止。
*
* 你的任务是解决一般性的问题: 设3个杯子的容量分别为a, b, c,最初只有第3个杯子装
* 满了c升水,其他两个杯子为空。最少需要倒多少升水才能让某一个杯子中的水有d升呢?如
* 果无法做到恰好d升,就让某一个杯子里的水是d'升,其中d'<d并且尽量接近d。
* (1≤a,b,c,d≤200)。要求输出最少的倒水量和目标水量(d或者d')。
*
*/
public class Main {
public static int goal;// 需要获取的最终水量
public static int[] glassMax = new int[3];// 各个杯子最大容积
public static Node originNode;// 初始状态杯子内存有的水量
public static int nearestNum = -1;// 最接近目标水量的水量
public static int lowest = Integer.MAX_VALUE; // 最接近目标水量或已达到最优水量的最少倒水量
public static Node lowestNode;// //最接近目标水量或已达到最优水量的最少倒水量情况(结点)
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
glassMax[0] = sc.nextInt();
glassMax[1] = sc.nextInt();
glassMax[2] = sc.nextInt();
goal = sc.nextInt();
initOriginNode();// 初始化originNode节点
BlockOut();// 此方法利用多线程限制时间,用于防止计算超时,给出当前可行解(未必是最优解,碰运气!)
try {
bfs();// 通过广度优先生成树并检索最优解
printResult();// 输出最优解
} catch (Exception e) {
}
System.exit(0);
}
}
/**
* 此方法利用初始化originNode节点
*/
public static void initOriginNode() {
int maxIndex = -1;
int maxValue = -1;
for (int i = 0; i < glassMax.length; i++) {
if (glassMax[i] >