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

一、实验内容

编程并实现:

  1. 写出判断𝑎在模𝑚下是否有逆元,如果有求出逆元的程序。
  2. 写出求𝑛的欧拉函数值𝜑(𝑛)的程序。

二、实验目标

1. 通过编写逆元求解程序,深入理解数论中的逆元概念,以及如何通过扩展欧几里得算法求解逆元,掌握模运算的基本原理和应用。

2. 通过编写求欧拉函数的程序,掌握如何运用欧拉函数的性质和质因数分解的思想来解决问题,进一步理解数论中的基本函数及其应用场景。

3. 通过这些数论算法的编程实践,提升算法实现能力,加深对数论概念的理解,并掌握相应问题的解决方法。

三、实验原理

(一)问题一原理

2.3节定理4

m是一个正整数,a是满足(a, m)=1的整数,则存在整数a'1a'<m使得aa'1(mod m),其中称a'(mod m)a逆元

1.3节定理5

a, b是任意两个正整数,则存在整数st使得:sa+tb=(a, b)

1.3节定理3

a, b, c是三个不全为零的整数。若a=bq+c,其中q是整数,则有(a, b)=(b, c)

(二)问题二原理

2.3节定理7

设正整数n有标准因数分解式

四、实验设计

(一)题1:写出判断𝑎在模𝑚下是否有逆元,如果有求出逆元的程序。

本题的起始思路是由2.3节定理4得来的:设m是一个正整数,a是满足(a, m)=1的整数,则存在整数a',1≤a'<m使得aa'≡1(mod m),其中称a'是(mod m)下a的逆元。

因此,判断a在模m下有无逆元的方法还是很简单的,只要求出(a, m)的值,然后判断它是否为1即可:如果(a, m)=1,则存在逆元;如果(a, m)!=1,则不存在逆元。

那么逆元的值怎么求呢?根据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)范围内即可。

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

接下来,进行编程实现,代码如下:

#include <iostream>
using namespace std;

// 使用广义欧几里得算法计算a和b的最大公因数,并求出x和y使得ax+by=gcd(a,b)
int extended_gcd(int a,int b,int &x,int &y){
    if (b==0) {
        x=1;
        y=0;
        return a; // 返回最大公因数
    }
    int x1, y1;
    int gcd=extended_gcd(b,a%b,x1,y1);
    x=y1;
    y=x1-(a/b)*y1;
    return gcd;
}

// 判断a在模m下是否有逆元,并求出逆元
bool modular_inverse(int a,int m,int &inverse){
    int x,y;
    int gcd=extended_gcd(a,m,x,y);

    // 如果gcd(a,m)!=1,则a在模m下没有逆元
    if(gcd!=1){
        return false;
    }

    // a在模m下有逆元,计算逆元并将其标准化到[0, m)范围内
    inverse=(x%m+m)%m;
    return true;
}

int main(){
    int a,m;
    cout<<"请输入a和m的值:";
    cin>>a>>m;
    int inverse;
    if(modular_inverse(a,m,inverse)){
        cout<<"a在模m下的逆元是: "<<inverse<<endl;
    }
    else{
        cout<<"a在模m下没有逆元"<<endl;
    }
    return 0;
}

我们来解释一下代码。首先,输入a和m的值后,定义逆元inverse,然后进入函数modular_inverse(a,m,inverse)。这个函数是用来判断a在模m下是否有逆元,并求出逆元的。根据modular_inverse()的返回值是ture or false,可以得到对应的输出。

而在modular_inverse()函数中,定义使得xa+ym=(a, m)的x和y,然后使用extended_gcd(a,m,x,y)函数算出a和m的最大公因数gcd。接着进行判断,如果gcd不为1,说明a在模m下没有逆元,直接返回false;如果gcd为1,说明a在模m下有逆元,返回true。然而,算出来的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)这个范围内。

接下来,我们来具体看一下extended_gcd()这个函数。

这个函数按照定理3的原理来工作,我们先来解释一下代码。

// 使用广义欧几里得算法计算a和b的最大公因数,并求出x和y使得ax+by=gcd(a,b)
int extended_gcd(int a,int b,int &x,int &y){
    if (b==0) {
        x=1;
        y=0;
        return a; // 返回最大公因数
    }
    int x1, y1;
    int gcd=extended_gcd(b,a%b,x1,y1);
    x=y1;
    y=x1-(a/b)*y1;
    return gcd;
}

函数接收两个整数a和b,以及两个引用变量x和y,这两个引用变量将用来存储x和y的值(即:ax+by=gcd(a, b)中的x和y)。首先,在传入a和b后,判断此时的b是否为0,这是递归的基线条件。如果为0则表示上一层的余数为0,则此时的a也即上一层的除数就是我们要的最大公因数。同时,要设置x=1,y=0,因为此时a×1+b×0=gcd(a, b)。

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

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

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

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

整理上式后得到:

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

至此,函数结束,我们得到了需要的gcd和x。

我们用课上的例子来走一下整个extended_gcd函数的流程。

第一层,a=1859,b=1573≠0,则定义当前层x1,x2后,进入第二层;

第二层,a=1573,b=286≠0,则定义当前层x1,x2后,进入第三层;

第三层,a=286,b=143≠0,则定义当前层x1,x2后,进入第四层;

第四层,a=143,b=0,此时设置x=1,y=0,并返回最大公因数a=143,返回第三层;

第三层,gcd=143,a/b=2,x=y1=0,y=x1-(a/b)*y1=1,返回最大公因数143,返回第二层;

第二层,gcd=143,a/b=5,x=y1=1,y=x1-(a/b)*y1=-5,返回最大公因数143,返回第二层;

第一层,gcd=143,a/b=1,x=y1=-5,y=x1-(a/b)*y1=6 ,返回最大公因数143。

函数结束后我们得到,(1859, 1573)=143,x=-5,y=6使得ax+by=(a, b)。

以上就是题1代码的全部解释。

(二)题2:写出求𝑛欧拉函数值𝜑(𝑛)的程序

本题的起始思路是由2.3节定理7得来的:

设正整数n有标准因数分解式

因此,我们的首要目标就是求出n的所有不同的质因数,并且在每次求得一个质因数后,要去除它的所有幂次。因为正整数n有标准因数分解式:

所以当把n中pi的所有幂次都去除以后,才能保证剩下的都是别的质因数的幂次。

而求质因数,只需要从2开始进行循环遍历即可。

进行编程实现,代码如下:

#include <iostream>
using namespace std;

// 求欧拉函数值的函数
int euler_totient(int n) {
    int result=n; // 初始值为n
    for(int p=2;p*p<=n;p++){
        // 如果p是n的质因数
        if(n%p==0){
            while(n%p==0)
                n/=p; // 去除p的所有幂次
            result-=result/p; // 根据公式计算欧拉函数
        }
    }
    // 如果n是一个大于sqrt(n)的质数
    if(n>1)
        result-=result/n;
    return result;
}

int main() {
    int n;
    cout<<"请输入一个正整数n: ";
    cin>>n;
    cout<<"欧拉函数φ("<<n<<")="<<euler_totient(n)<<endl;
    return 0;
}

代码的核心在于euler_totient()函数,它首先定义result变量并赋初值n。然后从2开始遍历质因数p,逐步检测n是否可以被p整除。如果p是n的质因数,先将n中p的所有幂次去掉,再使用公式result=result×(1−1/p​)=result−result/p​逐步更新欧拉函数值。由于乘法是对称的,如12=2×6=6×2,因此在遍历因子的时候,我们不需要从p=2一直遍历到n,只需要遍历到sqrt(n)即可。

遍历结束后,意味着我们已经处理完所有小于等于sqrt(n)的质因数,这时可能会出现以下两种情况:

  1. n经过所有质因数的处理后,已经变成了1。这说明n原来的所有质因数已经被完全分解。
  2. n还剩下一个大于sqrt(n)的质数,这是因为在分解过程中,没有质数能够整除它。这种情况下,说明n自身是一个质数。

例如,对于n=13,所有小于等于sqrt(n)的质数(2和3)都不能整除13,因此经过循环之后,n仍然是13这个质数。

对于第二种情况,如果n在完成循环后仍然是一个大于sqrt(n)的质数,则需要将其本身这个质因数考虑进去。

在代码中,体现为在循环结束后,检查n是否大于1,如果n还大于1,说明n本身是一个质数,并且在循环中没有被分解,要再用公式result=result×(1−1/p​)=result−result/p​更新欧拉函数值。如果n不大于1,说明n其已经在循环中被分解完毕了,则不用对结果result做进一步处理。

最终,result即为n的欧拉函数的最终值。

五、实验结果

(一)题1:写出判断𝑎在模𝑚下是否有逆元,如果有求出逆元的程序。

输入两个测试样例:a=2、m=7和a=1859、m=1573,程序运行结果如下:

可以看到,结果均正确。

(二)题2:写出求𝑛欧拉函数值𝜑(𝑛)的程序

输入两个测试样例:8和13,程序运行结果如下:

可以看到,结果均正确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值