首页 > 代码库 > yzoi1777倒水问题的详细解法

yzoi1777倒水问题的详细解法

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>0) 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=0;//即x中的水全部倒入y中,x变为空12              }13          }

 

②x->z时

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

 1         if(x>0) 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=0;12             }13         }

③y->x时

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

1         if(y>0)2         {3             x=x+y;//y倒空4             y=0;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>0) 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=0;12             }13         }    

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

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

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

struct node{    int qx,qy,qz;    int pre;} q[maxn];

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

每倒一次水,就想当于产生了一个新的状态,于是就要判断这个状态是否已达到目标。因此每倒一次水,就要调用一次push过程,将新状态入队,然后判断队尾元素是否已是解,push的代码如下:
 1 void push() 2 { 3     bool can_push=true; 4     for(int i=1;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正是帮助完成了这一点.

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

void is_target(){    if(q[rear].qx==n||q[rear].qy==n||q[rear].qz==n)        flag=true;    else        flag=false;}

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

void initial(){    x=q[front].qx;    y=q[front].qy;    z=q[front].qz;    }

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

  1 void pour()  2 {  3     while(front <= rear)  4     {  5         initial();  6         //x->y  7         if(x>0)  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=0; 18             } 19         } 20         push(); 21         is_target(); 22         if(flag) 23             break; 24         //x->z 25         initial(); 26         if(x>0) 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=0; 37             } 38         } 39         push(); 40         is_target(); 41         if(flag) 42             break; 43         //y->x 44         initial(); 45         if(y>0) 46         { 47             x=x+y; 48             y=0; 49         } 50         push(); 51         is_target(); 52         if(flag) 53             break; 54         //y->z 55         initial(); 56         if(y>0) 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=0; 67             } 68         } 69         push(); 70         is_target(); 71         if(flag) 72             break; 73         //z->x 74         initial(); 75         if(z>0) 76         { 77             x=x+z; 78             z=0; 79         } 80         push(); 81         is_target(); 82         if(flag) 83             break; 84         //z->y 85         initial(); 86         if(z>0) 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=0; 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=10000+10; 12 int front,rear;  13 int xmax,ymax,zmax; 14 int n; 15 int x,y,z; 16 int step=0; 17 int k=0; 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=1;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>0) 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=0; 79             } 80         } 81         push(); 82         is_target(); 83         if(flag) 84             break; 85         //x->z 86         initial(); 87         if(x>0) 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=0; 98             } 99         }100         push();101         is_target();102         if(flag)103             break;104         //y->x105         initial();106         if(y>0)107         {108             x=x+y;109             y=0;110         }111         push();112         is_target();113         if(flag)114             break;115         //y->z116         initial();117         if(y>0)118         {119             if(y+z>zmax)120             {121                 y=y-(zmax-z);122                 z=zmax;123             }124             else125             {126                 z=y+z;127                 y=0;128             }129         }130         push();131         is_target();132         if(flag)133             break;134         //z->x135         initial();136         if(z>0)137         {138             x=x+z;139             z=0;140         }141         push();142         is_target();143         if(flag)144             break;145         //z->y146         initial();147         if(z>0)148         {149             if(z+y>ymax)150             {151                 z=z-(ymax-y);152                 y=ymax;153             }154             else155             {156                 y=y+z;157                 z=0;158             }159         }160         push();161         is_target();162         if(flag)163             break;        164         front++;                    165     }166 }167 int main()168 {169     front=1,rear=1;170     cin>>xmax>>ymax>>zmax;171     cin>>n;172     q[front].qx=xmax;173     q[front].qy=0;174     q[front].qz=0;175     pour();176     177     178     if(flag)179     {180     181     182     while(q[rear].pre>0)183     {184         k++;185         data[k].x=q[rear].qx;186         data[k].y=q[rear].qy;187         data[k].z=q[rear].qz;188         rear=q[rear].pre;189     }190     if(k>1)191         cout<<k<< <<"steps"<<endl;192     else193         cout<<k<< <<"step"<<endl;194     cout<<"step0:"<< <<xmax<< <<0<< <<0<<endl;195     for(int i=k;i>=1;i--)196     {197         cout<<"step"<<k-i+1<<:<< <<data[i].x<< <<data[i].y<< <<data[i].z<<endl; 198     }199     }200     else201     {202         cout<<"No Solution"<<endl;203     }204     return 0;205 }

 

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

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

 

 

 

yzoi1777倒水问题的详细解法