HNU信息安全数学基础实验三

一、实验内容

编程实现:中国剩余定理

要求:1)输入数组n,m[],b[],输出结果;

           2)不能直接调用现有函数库中函数。

二、实验目标

1. 掌握中国剩余定理的原理及其应用场景。

2. 熟悉使用编程实现中国剩余定理的步骤和算法。

3. 理解模逆元的概念,并通过编程实现模逆元的求解。

三、实验原理

3.2节定理1:中国剩余定理

设m₁, m₂, ..., mₖ是k个两两互素的正整数, 则对任意的整数b₁, b₂, ..., bₖ, 同余式组

一定有解, 且在同余意义下解是唯一的。

  1. 若令

则同余式组的解可表示为:

其中

即Mᵢ'是Mᵢ的模mᵢ逆元。

四、实验设计

题目:输入数组nm[]b[],由中国剩余定理,输出同余式组的解

本题的解题思路只需按照中国剩余定理一步步来即可。

首先,由于中国剩余定理需要满足的前提条件是:输入的模m₁, m₂, ..., mₖ是k个两两互素的正整数。因此我们要在获取输入后先进行检查。

检查功能的核心代码如下:

// 计算最大公因数gcd
int gcd(int a, int b){
    return (b==0)? a:gcd(b,a%b);
}

// 检查模数是否两两互素
bool areCoprime(const vector<int> &m,int n){
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            if(gcd(m[i],m[j])!=1){
                return false; // 如果有两个模数不是互素的,立刻返回 false
            }
        }
    }
    return true; // 如果所有模数两两互素,返回 true
}

我们使用areCoprime判断模数是不是两两互素的,一旦有两个模数不是互素的,就立刻返回false,表示中国剩余定理的前提未满足,无法继续执行;只有当所有模数两两互素时,才返回true,表示前提满足。

在检查完前提后,我们正式开始中国剩余定理的主体部分,编程实现如下:

// 实现中国剩余定理
int chineseRemainder(vector<int> &m,vector<int> &b,int n,int M){
    int result=0;
    for (int i=0;i<n;i++){
        int Mi=M/m[i];
        result+=modInverse(Mi,m[i])*Mi*b[i];
    }
    return result%M;
}

参数m[]是存放模数的数组,参数b[]是存放每个同余式方程余数的数组,参数n是同余式组中同余式的个数,参数M即模m₁, m₂, ..., mₖ的乘积。

我们首先定义结果result为0,然后通过循环每次求出一个Mi,并使用公式result+=modInverse(Mi,m[i])*Mi*b[i]来更新result的值。其中,modInverse(Mi,m[i])这个函数求得的即Mi在模m[i]下的逆元Mᵢ'。

在循环结束后,我们返回result取余M的值,即为最终我们需要求的同余式组的解也即本题的答案。

至于逆元的值怎么求,在实验一中我们已经有过了具体分析:

根据1.3节定理5:设a, b是任意两个正整数,则存在整数s和t使得:sa+tb=(a, b)。由此我们知道,对于a和m,则一定存在整数x和y,使得xa+ym=(a, m)。若a在模m下有逆元,此时(a, m)=1,则有xa+ym=1。对于这个式子,根据同余的定义,也就是ax≡1(mod m)。因此,x即为a的逆元,我们只要求出x,并把它标准化到[0, m)范围内即可。

现在,问题转化为了如何求出使得xa+ym=(a, m)的x。显然,利用广义欧几里得算法和1.3节定理3:设a, b, c是三个不全为零的整数。如果a=bq+c,其中q是整数,则有(a, b)=(b, c),我们可以轻松得到我们需要的值。

编程实现代码如下:

// 使用广义欧几里得算法求出使得ax+my=gcd(a,m)的x和y
void extended_gcd(int a,int m,int &x,int &y){
    if (m==0){
        x=1;
        y=0;
        return;
    }
    int x1,y1;
    extended_gcd(m,a%m,x1,y1);
    x=y1;
    y=x1-(a/m)*y1;
    return;
}

// 求a在模m下的逆元
int modInverse(int a,int m){
    int x,y,inverse;
    extended_gcd(a,m,x,y);
    inverse=(x%m+m)%m;
    return inverse;
}

在函数modInverse ()中,定义使得xa+ym=(a, m)的x和y以及逆元inverse,然后使用extended_gcd(a,m,x,y)函数进行广义欧几里得算法的处理,这样我们可以得到x的值。然而,算出来的x可能是正数也可能是负数,并且范围也不确定,我们需要把它标准化到[0, m)的范围内。总的操作是inverse=(x%m+m)%m,其分步含义如下:

x%m:这个操作先将x转换成一个在(-m, m)之间的数。如果x是负数,x%m通常会给出一个负的同余结果。

例如,假设x=-3且m=5,则x%m=-3。

x%m+m:这个步骤的目的是确保结果是非负数。我们将x%m加上m,这样无论x%m是什么值,最终结果都会在(0, 2m)之间。

继续上面的例子,x%m+m=-3+5=2。

再次取模(x%m+m)%m:这一步确保结果是一个在[0, m-1]之间的数。如果前面x%m的结果已经是非负数,这个操作不会改变它。如果前面x%m是负数,加上m后,这个操作会确保最终结果在0到m-1之间。

在我们的例子中,(x%m+m)%m=2%5=2。

最终,这个表达式确保了逆元x在[0, m)这个范围内。

// 使用广义欧几里得算法求出使得ax+my=gcd(a,m)的x和y
void extended_gcd(int a,int m,int &x,int &y){
    if (m==0){
        x=1;
        y=0;
        return;
    }
    int x1,y1;
    extended_gcd(m,a%m,x1,y1);
    x=y1;
    y=x1-(a/m)*y1;
    return;
}

至于extended_gcd()这个函数,它接收两个整数a和m,以及两个引用变量x和y,这两个引用变量将用来存储x和y的值(即:ax+my=gcd(a, m)中的x和y)。首先,在传入a和m后,判断此时的m是否为0,这是递归的基线条件。如果为0,由(a, 0)=a得此时的a就是最大公因数。同时,要设置x=1,y=0,因为此时a×1+m×0=gcd(a, m)。

如果m不为0,则设置x1,y1后继续递归进入extended_gcd函数,函数参数分别为m,a%m,x1,y1。即我们求出m和a%m的最大公因数,并返回相应的x1和y1使得mx1+(a%m)y1=gcd(m, a%m)。

在上面的extended_gcd函数返回后,我们可以通过递归结果更新当前 x 和 y 的值。

我们知道,在递归调用extended_gcd(m, a%m, x1, y1)之后,得到的解满足:

通过欧几里得算法可以写成:

整理上式后得到:

所以,我们可以更新x=y1,y=x1-(a/m)*y1。

当extended_gcd()函数返回后,我们就可以得到需要的x。

本题总的代码如下:

#include <iostream>
#include <vector>
using namespace std;

// 计算最大公因数gcd
int gcd(int a, int b){
    return (b==0)? a:gcd(b,a%b);
}

// 检查模数是否两两互素
bool areCoprime(const vector<int> &m,int n){
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            if(gcd(m[i],m[j])!=1){
                return false; // 如果有两个模数不是互素的,立刻返回 false
            }
        }
    }
    return true; // 如果所有模数两两互素,返回 true
}

// 使用广义欧几里得算法求出使得ax+my=gcd(a,m)的x和y
void extended_gcd(int a,int m,int &x,int &y){
    if (m==0){
        x=1;
        y=0;
        return;
    }
    int x1,y1;
    extended_gcd(m,a%m,x1,y1);
    x=y1;
    y=x1-(a/m)*y1;
    return;
}

// 求a在模m下的逆元
int modInverse(int a,int m){
    int x,y,inverse;
    extended_gcd(a,m,x,y);
    inverse=(x%m+m)%m;
    return inverse;
}

// 实现中国剩余定理
int chineseRemainder(vector<int> &m,vector<int> &b,int n,int M){
    int result=0;
    for (int i=0;i<n;i++){
        int Mi=M/m[i];
        result+=modInverse(Mi,m[i])*Mi*b[i];
    }
    return result%M;
}

int main(){
    int n;
    cout<<"请输入同余式的数量: ";
    cin>>n;
    vector<int> m(n),b(n);
    cout<<"请输入每个同余式的余数b[i]和模数m[i]:\n";
    for(int i=0;i<n;i++){
        cout<<"b["<<i<<"]: ";
        cin>>b[i];
        cout<<"m["<<i<<"]: ";
        cin>>m[i];
    }
    // 检查模数是否两两互素
    if(!areCoprime(m,n)){
        cout<<"错误: 输入的模数不是两两互素的,无法应用中国剩余定理!"<<endl;
        return 1;
    }
    int M=1;
    for(int i=0;i<n;i++)
        M*=m[i];
    int result=chineseRemainder(m,b,n,M);
    cout<<"同余式组的解为: X≡"<<result<<"(mod "<<M<<")"<<endl;
    return 0;
}

 五、实验结果

题目:输入数组nm[]b[],由中国剩余定理,输出同余式组的解

输入课上韩信点兵的例子:

程序运行结果如下:

可以看到,结果正确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值