孙子问题(同余定理)

时间:2025-02-09 08:01:49

描述
我国古代《孙子算经》中,记有如下算题:“今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?”并给出得数:“答曰:23。”

为解决这个问题民间流传了如下歌诀:“三人同行七十稀,五树梅花廿一枝,七子团员正半月,除百零五便得知。”

把上面的问题说得明白一点就是:有一个正整数N,除以3的余数是2,除以5的余数是3,除以7的余数是2,要求这个数。

民间给出的解法是:把N除以3的余数乘以70,把N除以5的余数乘以21,把N除以7的余数乘以15,把这三个结果加起来,最后把得到的结果除以105得到的就是答案(能满足条件的最小数)。

其实在民间的解法中不除以105得到的也是一个符合题意的答案,而且民间的解法对于已知“除以3的余数,除以5的余数和除以7的余数” 的问题都能得到一个符合要求的答案。比如对于上面的问题,得到的结果是2 * 70 + 3 * 21 + 2 * 15 = 233,这个结果也能满足除以3的余数是2,除以5的余数是3,除以7的余数是2。如果已知的问题是“除以3的余数是1,除以5的余数是4,除以7的余数是4”,民间解法得到的结果1 * 70 + 4 * 21 + 4 * 15 = 214,这个结果也满足除以3的余数是1,除以5的余数是4,除以7的余数是4。

问题总结:根据3 ,5, 7求得70,21,15

把这个问题推广到更普遍的情况:

孙子问题:对于给定的正整数a1, a2, ... an,是否存在正整数b1, b2, ... bn,使得对于任意的一个正整数N,如果用N除以a1的余数是p1,用N除以a2的余数是p2……用N除以an的余数是pn,那么M = p1 * b1 + p2 * b2 + ... + pn * bn能满足M除以a1的余数也是p1,M除以a2的余数也是p2……M除以an的余数也是pn。

输入
输入包括多组测试数据,每组数据包括一行。在每组数据中,首先给出ai的个数n (1 <= n <= 10),然后给出n个不大于50的正整数a1, a2, ... an。最后一组测试数据中n = 0,表示输入的结束,这组数据不用处理。
输出
对于每一组测试数据,输出一行,如果存在正整数b1, b2, ... bn满足题意,则输出这n个正整数(数的长度不要超过50位)如果有多组答案,输出任意一组即可,相邻的正整数之间用一个空格隔开;否则,输出“NO”。
样例输入
3 3 5 7
0
样例输出
70 21 15提示

问题总结:根据a1, a2, ... an求b1, b2, ... bn的过程,与N ,M无关

求b1的过程

求a2, ... an的最小公倍数,如果a2, ... an的最小公倍数除以a1余1,则b1等于a2, ... an的最小公倍数,否则求最小公倍数的1,2,3....ai-1倍,找到除以 a1余1的数,b1即等于该数,如果还没有找到除以 a1余1的数,就b1不存在。

问题:为什么要求a2, ... an的最小公倍数?

解决:

以3,5,7为例

假设n1%3=2    n2%5=3  n3%7=2   

由于有数学定理: 如果a%b=c  那么(a+k*b)%b=c;   如果n1+n2+n3满足除以3余2,除以5余3,除以7余2,那么n1需要是5,7的倍数,同时n1除以3余2,n2是3,7的你倍数,同时除以5余3,n3是3,5 的倍数同时除以7余2

问题:求过5,7最小公倍数,为什么要找最小公倍数的倍数除以3余1的数

解决:

因为上面条件说了n1需要是5,7的倍数,同时n1除以3余2

数学定理:如果a%b=1, 也就是说(k*b+1)%b=1   那么n*(k*b+1)%b=1*n;(n<b)



推广求bi的过程:

求除ai外的n-1个元素的最小公倍数,如果最小公倍数除以ai余1,则bi等于最小公倍数,否则求最小公倍数的1,2,3....倍,找到除以 ai余1的数,bi即等于该数,如果还没有找到除以 ai余1的数,就bi不存在。

求b1过程解析:

以3,5,7求70,21,15的过程为例

1.     5,7的最小公倍数(35)+3,7的最小公倍数(21)+3,5的最小公倍数(15)的值是3,5,7的公倍数

2.    5,7的最小公倍数(35)除以3余2,35*2=70除以3余1,也就是说3*n+1=70   所以(3*n+1)*2%3==2     即(3*n+1)*余数%3==余数     

3.    此时b1的值就是70=5*7*2;除以5,7能除尽,同时除以3余1.

以上求得bi的值一定满足p1 * b1 + p2 * b2 + ... + pn * bn的结果M除以a1的余数为p1,除以a2的余数为p2……除以an的余数为pn。但是不是最小值,例如5*7+3*7*3+3*5*2=128也满足除以3余2,除以5余3.,除以7余2,因为5,7的最小公倍数除以3正好余2,所以不用乘以2,但是上面的求bi的方法是统一的方法。

孙子问题源程序:


import .*;
public class LX3 {

    private static int n;//整数的个数
    private static int[] a;//保存n个整数a1,a2.....an
    private static int[] b;//保存n个整数b1,b2.....bn
    private static int[] b1;//保存n个整数,每个整数分别代表除ai值外的数的最小公倍数
    private static int[] tp;//在求bi的时候的辅助数组
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Scanner sc=new Scanner();
        n=();
        a=new int[n];
        b=new int[n];
        b1=new int[n];
        tp=new int[n];
        for(int i=0;i<n;i++)
        {
            a[i]=();
        }
        

        for(int i=0;i<n;i++)
        {
            for(int p=0;p<n;p++)//数组a中的元素不能改变,但是在求最小公倍数的时候需要改变元素的值所以
                                //没求一次最小公倍数需要给临时数组tp重新赋予a数组的值
            {
                tp[p]=a[p];
            }
            MinCommonMutiply(i);
            //(b1[i]);
        }
        for(int i=0;i<n;i++)
        {
            if(b1[i]%a[i]==1)
            {
                b[i]=b1[i];
                (b[i]+" ");
            }
            else
            {
                int m=0;
                for( m=2;m<a[i];m++)
                {
                    if(b1[i]*m%a[i]==1)
                    {
                        b[i]=b1[i]*m;
                        (b[i]+" ");
                        break;
                    }
                }
                if(m==a[i])//没有的到除以a[i]余1的数
                {
                    ("NO"+" ");
                }
            }
        }
        
    }
    
    public static void MinCommonMutiply(int i)//除了a[i]之外的数的最小公倍数
    {
        int temp1=tp[i];
        tp[i]=1;
        int yinzi=1;
        for(int j=0;j<n-1;j++)
        {
            for(int k=j+1;k<n;k++)
            {
                int num1,num2;
                if(tp[j]<tp[k])//两数最大数保存在 num1中
                {
                    num1=tp[k];
                    num2=tp[j];
                }
                else
                {
                    num1=tp[j];
                    num2=tp[k];
                }
                while(num2!=0)
                {
                    int temp=num1%num2;
                    num1=num2;
                    num2=temp;
                }
                tp[j]/=num1;
                tp[k]/=num1;
                yinzi*=num1;
                //(j+" "+k+" "+a[j]+" "+a[k]+" "+yinzi);
            }
        }
        //(yinzi);
        for(int j=0;j<n;j++)
        {
            yinzi*=tp[j];
        }
        b1[i]=yinzi;    
    }

}