泊松分酒

本文探讨了泊松分酒问题,这是一个经典的智力测试和过程模拟案例。通过使用两个不同容量的空杯(5品脱和8品脱),目标是将12品脱的酒平分。文章详细介绍了模拟分酒操作的设计,包括两种分倒顺序,并提供了C语言实现的程序设计和运行示例。此外,还讨论了数据输入满足何种条件时才能确保问题有解。

泊松分酒是一个著名的智力测试题,也是一个有难度的过程模拟经典案例;

1.案例提出:

法国数学家泊松(Poisson)曾提出以下分酒趣题:

  • 某人有一瓶12品脱(容量单位)的酒,同时有容积为5品脱与8品脱的空杯各一个,借助这两个空杯,如何将这瓶12品脱的酒平分?

我们要解决一般的平分酒案例:

  • 借助容量分别为bv与cv(单位为整数)的两个空杯,用最少的分倒次数把总容量为偶数a的酒平分,这里正整数bv、cv与偶数a均从键盘输入;

2.模拟设计:

求解一般的“泊松分酒”问题:借助容积分别为整数bv、cv的两个空杯,用最少的分倒次数把总容量为偶数a的酒(并未要求满瓶)平分,采用直接模拟平分过程的分倒操作;

为了把键盘输入的偶数a通过分倒操作平分为两个i: i=a/2 (i为全局变量),设在分倒过程中:

瓶A中的酒量为a(0<=a<=2*i)

杯B(容积为bv)中的酒量为b(0<=b<=bv)

杯C(容积为cv)中的酒量为c(0<=c<=cv)

我们模拟下面两种方向的分倒操作:

(1)、按 A→B→C 顺序分倒操作;

  • ①、当B杯空(b=0)时,从A瓶倒满B杯;

  • ②、从B杯分一次或多次倒满C杯: 若b>cv-c,倒满C杯,操作③; 若b<=cv-c,倒空B杯,操作①;

  • ③、当C杯满(c=cv)时,从C杯倒回A瓶,分倒操作过程中,用变量n统计分倒次数,每分到一次n增1: 若b=0且a< bv时,步骤①无法实现(即A瓶的酒倒不满B杯)而中断,记n=-1为中断标志,分倒操作过程中若有a=i或b=i或c=i时,显然已达到平分目的,分倒循环结束,用试验函数Probe(a,bv,cv)返回分倒次数n的值; 否则,继续循环操作;

模拟操作描述:

while(!(a==i || b==i || c==i))
{
   if(!b)                /*从A瓶倒满B杯*/
   {
      a-=bv;
      b=bv;
   }
   else if(c==cv)        /*从C杯倒回A瓶*/
   {
      a+=cv;
      c=0;
   }
   else if(b>cv-c)       /*从B倒满C杯*/
   {
      b-=(cv-c);
      c=cv;
   }
   else                  /*从B倒C,倒空B杯*/
   {
      c+=b;
      b=0;
   }
   printf("%6d%6d%6d\n",a,b,c);
}

(2)、按 A→C→B 顺序分倒操作;

这一循环操作与(1)实质上是C与B杯互换,相当于返回函数值Probe(a,cv,bv);

试验函数Probe()的引入是巧妙的,可综合模拟以上两种分倒操作,避免了关于cv与bv大小关系的讨论;

同时设计实施函数Practice(a,bv,cv),与试验函数相比较,把n增1操作改变为输出中间过程量a、b、c,以标明具体操作进程;

在主函数main()中,分别输入a、bv,cv的值后,为寻求较少的分倒次数,调用试验函数并比较m1=Probe(a、bv、cv)与m2=Probe(a,bv,cv):

  • 若m1<0 且 m2<0,表明无法平分(均为中断标志);

  • 若m2<0,只能按上述(1)操作;

  • 若0< m1< m2,按上述(1)操作分倒次数较少(即m1),此时调用实施函数Practive(a,bv,cv);

  • 若m1<0,只能按上述(2)操作;

  • 若0< m1< m2,按上述(2)操作分倒次数较少(即m2),此时调用实施函数Practive(a,cv,bv);

实施函数打印整个模拟分倒操作进程中的a,b,c的值,最后打印出最少的分倒次数;

3.程序设计:

#include<stdio.h>
void practice(int,int,int);          /*调用函数声明*/
int i,n,probo(int,int,int);
int main()
{
   int a,bv,cv,m1,m2;
   printf("请输入酒总量(偶数):");
   scanf("%d",&a);
   printf("两空杯容量bv、cv分别为:");
   scanf("%d,%d",&bv,&cv);
   i=a/2;
   if(bv+cv<i)
   {
      printf("空杯容量太小,无法平分!\n");
      return;
   }
   m1=probo(a,bv,cv);
   m2=probo(a,cv,bv);
   if(m1<0 && (m2<0 || m1<m2))
   {
      n=m1;
      practice(a,bv,cv);
   }
   else
   {
      n=m2;
      practice(a,cv,bv);
   }
   void practice(int a,int bv,int cv)         /*模拟实施函数*/
   {
      int b=0,c=0;
      printf("平分酒的分发:\n");
      printf("酒瓶%d 空杯%d 空杯%d\n",a,bv,cv);
      printf("%6d%6d%6d\n",a,b,c);
      while(!(a==i || b==i || c==i))
      {
         if(!b)
         {
            a-=bv;
            b=bv;
         }
         else if(c=cv)
         {
            a+=cv;
            c=0;
         }
         else if(b>cv-c)
         {
            b-=(cv-c);
            c=cv;
         }
         else
         {
            c+=b;
            b=0;
         }
         printf("%6d%6d%6d\n",a,b,c);
      }
      printf("平分酒共分倒%d次\n",n);
   }
   int probo(int a,int bv,int cv)         /*试验函数*/
   {
      int n=0,b=0,c=0;
      while(!(a==i || b==i || c==i))
      {
         if(!b)
            if(a<bv)
            {
               n=-1;
               break;
            }
            else
            {
               a-=bv;
               b=bv;
            }
         else if(c==cv)
         {
            a+=cv;
            c=0;
         }
         else if(b>cv-c)
         {
            b-=(cv-c);
            c=cv;
         }
         else
         {
            c+=b;
            b=0;
         }
         n++;
      }
      return(n);
   }

4.程序运行示例及其注意事项:

请输入酒总量(偶数):12
两空杯容量bv、cv分别为:5,8
平分酒的方法:
酒瓶12      空杯8      空杯5
  12         0          0
  4          8          0
  4          3          5
  9          3          0
  9          0          3
  1          8          3
  1          6          5
平分酒共分倒6次

注意:

  1. 以上程序中,对m1和m2的全路径判断虽然可以获得分倒次数较少的方法,但这是建立在程序有解的前提之下,而程序有没有解并不能通过对m1和m2的全路径判断来完全确定。 例如,当输入a=10,bv=4,cv=6时,显然没有解,这是程序进入死循环

那么输入的数据在满足什么条件时才有解呢?

  1. 令d=gcd(bv,cv)表示bv和cv的最大公约数,且满足基本条件bv+cv>=(a/2)时,可以证明,当mod(a/2,d)=0时,所输入的数据一定有解,特别地,当bv与cv互质时,a为任意偶数都有解
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值