韩信点兵问题

韩信点兵是一个有趣的猜数游戏。如果你随便拿一把蚕豆(数目约在100粒左右),先3粒3粒地数,直到不满3粒时,把余数记下来;第二次再5粒5粒地数,最后把余数记下来;第三次是7粒一数,把余数记下来。然后根据每次的余数,就可以知道你原来拿了多少粒蚕豆了。不信的话,你还可以试验一下。例如,假如3粒一数余1粒,5粒一数余2粒,7粒一数余2粒,那么,原有蚕豆有多少粒呢? 


这类题目看起来是很难计算的,可是我国古时候却流传着一种算法,名称也很多,宋朝周密叫它“鬼谷算”,又名“隔墙算”;杨辉叫它“剪管术”;而比较通行的名称是“韩信点兵”。最初记述这类算法的是一本名叫《孙子算经》的书,后来在宋朝经过数学家秦九韶的推广,又发现了一种算法,叫做“大衍求一术”。这在数学史上是极有名的问题,外国人一般把它称为“中国剩余定理”。至于它的算法,在《孙子算经》上就已经有了说明,而且后来还流传着这么一道歌诀: 


三人同行七十稀, 


五树梅花廿一枝, 


七子团圆正半月, 


除百零五便得知。 


这就是韩信点兵的计算方法,它的意思是:凡是用3个一数剩下的余数,将它用70去乘(因为70是5与7的倍数,而又是以3去除余1的数);5个一数剩下的余数,将它用21去乘(因为21是3与7的倍数,又是以5去除余1的数);7个一数剩下的余数,将它用15去乘(因为15是3与5的倍数,又是以7去除余1的数),将这些数加起来,若超过105,就减掉105,如果剩下来的数目还是比105大,就再减去105,直到得数比105小为止。这样,所得的数就是原来的数了。根据这个道理,你可以很容易地把前面的五个题目列成算式:


1×70+2×21+2×15-105 


=142-105 


=37 


因此,你可以知道,原来这一堆蚕豆有37粒。
证明:

设三次数的数目分别为a1,a2,a3(3,5,7),三次的余数分别为b1,b2,b3(1,2,2);定义m为一魔法数,m的求法如下:

m是a,b,c的魔法数,则m mod a = 1,m mod b = 0, m mod c = 0;即m是b和c的公倍数,而且除以a的余数是1。由此我们可以求出m1,m2,m3。设sm是各个余数与mi的乘积的和。由此可得:

         sm  mod a1 = (m1*b1+m2*b2 + m3*b3 ) mod a1


     又因为m2,m3是a1的公倍数,所以sm mod a1 = (m1*b1) mod a1 = b1,即sm满足条件1.
同理得证,sm满足条件2和条件3。因此sm是满足条件的一个数。        
又因为如果有一个数x是a1,a2,a3的最小公倍数,则sm+k*x也是满足条件的一个数。由此可知,在(0~x)的范围内,有一个数是满足条件的。得证。


算法如下:


#include "stdafx.h"
int gcd(int a,int b)
{
   while ( b != 0 )
   {
      int temp = b;
      b = a % b;
      a = temp;
   }
   return a;
}

int mgcd(int a,int b)
{
   return (a*b)/gcd(a,b);
}

struct node_t
{
   int a;
   int b;
};
int getminm(int a,int b,int c)
{
   int m ;
   int r;
   r = m = mgcd(b,c);
   while ( r%a != 1) r += m;
   return r;
}
int getNum(node_t *pnode)
{
   int m1,m2,m3,x;
   int sm;
  
   m1 = getminm(pnode[0].a,pnode[1].a,pnode[2].a);
   m2 = getminm(pnode[1].a,pnode[0].a,pnode[2].a);
   m3 = getminm(pnode[2].a,pnode[0].a,pnode[1].a);

   x = mgcd(pnode[0].a,pnode[1].a);
   x = mgcd(pnode[2].a,x);

   sm = pnode[0].b * m1 + pnode[1].b *m2 + pnode[2].b * m3;
   return sm%x;
}

int main()
{
   node_t node[3];
   node[0].a = 3;
   node[0].b = 1;

   node[1].a = 5;
   node[1].b = 2;

   node[2].a = 11;
   node[2].b = 3;

   printf("蚕豆数:%d\n",getNum(node));
   return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值