niangniang~

韩信点兵的老问题啦~模线性方程~

韩信点兵

问题描述:

在《孙子算经》中有如下“韩信点兵”问题:

有兵一列,三三数之余二,五五数之余三,七七数之余二,问兵几何?

问题等价于求x使,

你的任务是从输入中读取若干组的数据,求解由n个同余式组成的同余方程的解。

输入:

输入有若干组数据。每组数据的第一行上有整数n,表示下面将有n个一次同余方程x≡bi(mod mi)组成一个同余方程组。接下来的t行的每一行上有2个整数bi和mi,(mi>0),它们之间用一个或几个空格隔开,表示一次同余方程x≡bi(mod mi)。现要求n个一次同余方程组成的同余方程组的解。

输出:

对每一组测试数据,每行输出该一次同余方程的小于m的最小非负解。

输入样例:

2

5 3

7 5

 

输出样例:

2

之前有在网络上看到个很特别的思路,就是先找个整数N=1,然后看他能否满足三三数之余二,五五数之余三,七七数之余二,不满足则加一,满足则程序结束,就是穷举法。当然不是不可以,但是遇到其他问题还是会有超时的危险,所以不是很建议。

我们还是走正统路线。这里介绍一下中国剩余定理。

中国剩余定理又称孙子定理,实话说之前有学数学的同志和我讨论过这个问题,当时我就是穷举来着。。。。后来发现显然是有更深的研究意义存在。

设m1,m2,...,mk是两两互素的正整数,即gcd(mi,mj) =1,i≠j,i,j = 1,2,...,k

 则同余方程组:
 x≡b1 (mod m1)
 x≡b2 (mod m2)
 ...
 x≡bk (mod mk)
 模[m1,m2,...,mk]有唯一解,即在[m1,m2,...,mk]的意义下,存在唯一的x,满足:
 x≡bi mod [m1,m2,...,mk],i = 1,2,...,k
这是最基本的数学解释,我们看看它的解的基本组成部分:
m=m1×m2×m3×m4×.....×mn
Mi=m/mi   Mi'是Mi的逆,也就是Mi与mi在gcd(Mi,mi)=1时候的最小非负x解。这里我们可以用扩展的欧几里德算法算出一个特解x,再用r = mi/gcd(M, mI);x=(x% r + r) % r ;做处理,求出最小非负x,就是Mi.
最后解的形式:a=(∑Mi×Mi'×bi)%m
证明暂时没有找到合适的版本。
下面是代码,我把计算a的过程也打印出来了,供作参考。
#include<iostream>
using namespace std;
long HX(int m[],int b[]);
int exGcd(int a, int b, int &x, int &y);
int gcd(int a,int b);
int n;

int main()
{
    
    cin>>n;
    int *b,*m;
    b=new int [n];
    m=new int [n];
    for(int i=0;i<n;i++)
    {
        cin>>b[i]>>m[i];
    }
    HX(m,b);
    return 0;
}

int gcd(int a,int b)
{
    if(a==0)
    {
        return b;
    }else
    {
        return gcd(b % a,a);
    }
}

long HX(int m0[],int b[])
{
    int x,y,m=1,a=0;
    int i;
    for(i=0;i<n;i++)  
        m=m*m0[i];
    for(int j=0;j<n;j++)
    {
        int MM=m/m0[j];
        exGcd(MM,m0[j],x,y);
        int r = m0[j]/gcd(MM, m0[j]);
        x=(x% r + r) % r ;  //要求的是最小非负
        a=(a+MM*x*b[j]);
        cout<<MM<<" "<<x<<" "<<b[j]<<endl; //测试结果
    }
    cout<<a%m<<endl;
    return 0;
}

int exGcd(int a, int b, int &x, int &y)
{
    if(b==0)
    {
        x=1;
        y=0;
     return a;
    }
    int r=exGcd(b,a%b,x,y);
    int t=x;
    x=y;
    y=t-a/b*y;
   return r;
}


完毕~