“韩信点兵”问题的数学真经

【题目描述】

相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入包含多组数据,每组数据包含3个非负整数a,b,c,表示每种队形排尾的人数(a<3,b<5,c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100。输入到文件结束为止。

【样例输入】

2 1 6

2 1 3

【样例输出】

Case 1: 41

Case 2: No answer

【题目来源】

刘汝佳《算法竞赛入门经典  第2版》习题2-2 韩信点兵(hanxin)

【解析】

一、题目求解:穷举法

思路很简单:从10到100逐个检查是否存在对3、5、7取余分别为a、b、c的数。

代码如下:

#include<stdio.h>
int main(){
    int a, b, c, kase=0;
    while(scanf("%d%d%d", &a, &b, &c) == 3){
        int flag=0;
        kase ++;
        for(int i=10; i<=100; i++){
            if(i%3==a && i%5==b && i%7==c) {
                flag = 1;
                printf("Case %d: %d\n", kase, i);
                break;
            }
        }
        if(0==flag) printf("Case %d: No answer\n", kase);
    }
    return 0;
}

本题的关键只有两个:

(1)结束输入问题。题目要求“输入到文件结束为止”,但为了程序的健壮性,最好还是用代码中的形式,而不要用下面的代码:

while (scanf("%d %d %d", &a, &b, &c) != EOF)

这个代码也符合题目要求,但是一旦输入个字母或者小数,你将瞬间体验到自动刷屏的快感。

(2)多组数据变量重置问题。此问题在《“多组数据”题的注意事项,天杀的“鲁棒性”》一文中已详细阐述过。变量flag是参与到每组的计算之中的,所以应该放到while循环之内进行初始化。

题目到这里就解完了,但是老金不由感慨:咱们用二十一世纪最强大的计算工具求解的问题,早在两千多年前那一位仅凭口算就能搞定。

将军真乃神人也!

相传故事是这样的:在楚汉相争时期,韩信曾率1500名将士与楚王大将李锋交战,战后返回大本营时遭遇楚军骑兵追击。韩信开启巧妙点兵模式,迅速计算出己方兵力,并成功击退敌军。

二、韩信点兵问题的高级算法

用现代数学的话说,韩信点兵的问题,本质上是一个同余方程组问题。

如果整数a和b对正整数m取模,得到的结果相同,那么我们就说a和b对模m同余。

以余数为2 1 6为例,韩信点兵问题的同余方程组如下:

x≡2(mod3)

x≡1(mod5)

x≡6(mod7)

这里的“≡”表示同余关系,“mod m”则表示对m取模。比如第一个同余方程,就表示x和2对模3同余,解这个同余方程就是要求x。

方程组的解法源自一本真经,叫《孙子算经》。从这本真经我得得出一个定理,叫中国剩余定理(Chinese Remainder Theorem,简称CRT)。

因为源自《孙子算经》,此定理又称孙子定理。它的意思是,对于任意一组两两互质的正整数m1, m2, ..., mk(模数3、5、7),以及任意一组整数a1, a2, ..., ak(余数2、1、6),都存在一个整数x,满足对于所有的i(1 ≤ i ≤ k),x对mi取模的结果等于ai,即x%mi = ai。这就是中国剩余定理的核心内容。

中国剩余定理的公式可以表示为:x = a1 * M1 * y1 + a2 * M2 * y2 + ... + ak * Mk * yk,其中Mi = M/mi,M是m1, m2, ..., mk的乘积;yi是Mi关于mi的模逆元,即Mi * yi ≡ 1 (mod mi)。

这个公式理解起来也很简单,因为x对任何一个mi取余,等号右边第i项之外的其它单项式都含有因子mi,因而都能被mi整除,故只有ai*Mi*yi这一项会产生余数。而咱们预先设定Mi*yi对mi的余数为1,所以ai*Mi*yi%m=ai,即上面的公式永远满足x%mi=ai。

对应到韩信点兵问题,x便是要求的一个解,mi就是模数3、5、7,ai是就x除以mi的余数。

以题目中的样例输入2 1 6为例:

按3人一排排列时,队尾剩下2人,即 x≡2(mod3);

按5人一排排列时,队尾剩下1人,即 x≡1(mod5);

按7人一排排列时,队尾剩下6人,即 x≡6(mod7)。

现在,我们应用中国剩余定理来求解这个同余方程组。步骤如下:

(1)分别求出上面公式等号右侧中的每一项,即每个ai*Mi*yi的值;

先计算所有模数的最小公倍数(LCM):M=3*5*7=105。

①对于第一个方程 n≡2(mod3),a1=2,M1= M/m1=M/3=35。

因为35×2≡1(mod3),所以y1=2。

最后,x1= a1*M1*y1=2*35*2=140。

②对于第二个方程 n≡1(mod5),类似地a2=1,M2=M/m2=105/5=21。

因为 21×1≡1(mod5),所以y2=1。

最后,x2=a2*M2*y2=1*21*1=21。

③对于第三个方程 n≡6(mod7),a3=6,M3=M/m3=105/7=15。

因为 15×1≡1(mod7),所以y3=1。

最后,x3= a3*M3*y3=6*15*1=90。

(2)将每一项值相加就是方程组的一个解:x=x1+x2+x3=140+21+90=251。

(3)将x加上或减去所有模数的最小公倍数(LCM)的n倍,可以得到方程组的其他解。

也就是说所有的解可以表示为:x+M*n(n为整数)。

本题要求总人数不小于10且不超过100,251-105*2=41,故41为最终解。

这个过程看起来很复杂,但如果理解了其原理,运用熟练了也是能很快算出答案的。因为三个模数是固定的3、5、7,所以Mi和yi的值都是固定的,即Mi分别为35、21、15,yi分别为2、1、1,因此x1、x2、x3的值可以直接用其余数乘以70、21、15即可求出。

明朝数学家程大位还为此解法编成《孙子歌诀》:“三人同行七十稀,五树梅花廿一枝,七子团圆月正半,除百零五便得知。”

不过即便是这样,也仍然要计算好几步乘法和加法,韩老师仅凭口算求解,足见其算力强大。

由于求出的解每加减105都是一个新的解,因此韩老师要保证估算人数误差不超过105人,即他能轻易分辨出总人数是1000人还是1105人,足见其眼力强大。

有传闻说韩信幼年得黄石传授《孙子算经》,因此算力大增。《孙子算经》成书约在四、五世纪,韩信生活在公元前3世纪,这样看来,韩老师还拥有一项穿越技能。

最后,再说明两点:

①此算法效率远高于咱们前面的穷举法。当同余方程组很复杂或者当数据范围变得很大时,此算法更适用。

②中国剩余定理要求所有的模数必须两两互质,即它们的最大公约数为1。在这个特定的例子中,3、5和7是互质的,因此可以使用中国剩余定理。如果模数不互质,则需要使用扩展中国剩余定理(Extended Chinese Remainder Theorem)来求解。

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金创想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值