第1关:快速指数运算
任务描述
本关任务:编写一个能快速实现指数运算的小程序。
相关知识
为了完成本关任务,你需要掌握:1.模运算,2.快速指数运算。
指数运算的优化
启示:如求x16
,直接计算的话需做15次乘法。然而如果重复对每个部分结果做平方运算即求x1
,x2
,x4
,x8
,x16
则只需4次乘法。 求am
可如下进行,其中a,m是正整数: 将m表示为二进制形式bkbk−1…b0
,即 m=bk2k+bk−12k−1+…+b121+b020
因此 例如: 23=1×24+0×23+1×22+1×21+1×20
a23=a1×24+0×23+1×22+1×21+1×20
=((((a1)2×a0)2×a1)2×a1)2×a1
=(((a2)2×a)2×a)2×a
快速指数算法
求am mod n
.将m表示为二进制形式bkbk−1…b0
d=1;
For i=k Downto 0
DO {
d=(d×d) mod n;
if bi=1 then
{ d=(d×a) mod n ; }
}
return d.
编程要求
根据提示,在右侧编辑器补充代码,输入十进制正整数a,m,n
.计算并输出am mod n
。
测试说明
平台会对你编写的代码进行测试:
测试输入:7 560 561
; 预期输出: 1
测试输入:20 3 11
; 预期输出: 3
开始你的任务吧,祝你成功!
/*
* quick.c
*
* Copyright 2020 yxm
*/
#include <stdio.h>
//ÔÚÏÂÃæBeginºÍEndÖ®¼ä²¹È«´úÂ룬Çó³ö Ä£nÏÂaµÄm´ÎÃÝ
/*********** Begin ***********/
int quick(int a,int m,int n)
{
int result=1;
while(m){
if(m&1){
result=(result*a)%n;
}
a=a*a%n;
m=m>>1;
}
return result;
}
/*********** End ***********/
int main(int argc, char **argv)
{
int a,m,n,d;
scanf("%d%d%d",&a,&m,&n);
d=quick(a,m,n);
printf("%d",d);
return 0;
}
第2关:扩展的欧几里得算法求乘法逆元
任务描述
欧几里德算法是用来求两个正整数最大公约数的算法。古希腊数学家欧几里德在其著作《TheElements》中最早描述了这种算法,所以被命名为欧几里德算法
本关任务:用扩展欧几里得算法求解乘法逆元。
相关知识
为了完成本关任务,你需要掌握:1.乘法逆元,2.欧几里得算法,3.扩展的欧几里得算法。
乘法逆元
如果gcd(a,b)=1
,那么: 存在a−1
,使a∗a−1≡1 mod b
存在b−1
,使b∗b−1≡1 mod a
这里,把a−1
称为a模b的乘法逆元,b−1
称为b模a的乘法逆元 下表是模7的乘法运算情况,可以看出1-6均存在乘法逆元,因为7和它们均为互素 下表中w−1
列即为模7下w
的乘法逆元 下表列出了模为8的乘法运算情况,可以看出,其中1、3、5、7存在模8的乘法逆元,2、4、6、8则不存在模8的乘法逆元。 下表中w−1
列即为模8下w
的乘法逆元
欧几里得算法
欧几里得(Euclid)算法是数论中的一个基本技术,是求两个正整数的最大公因子的简化过程。而推广的Euclid算法不仅可求两个正整数的最大公因子,而且当两个正整数互素时,还可求出其中一个数关于另一个数的乘法逆元。 算法基于以下定理: 对于任意非负整数a和任意正整数b,有 gcd(a,b)=gcd(b,amod b)
例如: gcd(55,22)=gcd(22,55mod22)=gcd(22,11)=11
得到欧几里得算法的简单描述如下:
Euclid(a,b)
if(b=0) then return a;
else return Euclid(b,a mod b);
Euclid算法的具体实现 Euclid算法可以给出两个正整数a和b的最大公因子,Euclid算法首先令r0
为a,令r1
为b(假设a>b),然后执行如下除法运算: 例如:对于gcd(1180,482)
1180=2∗482+216gcd(1180,482)482=2∗216+50gcd(482,216)216=4∗50+16gcd(216,50)50=3∗16+2gcd(50,16)16=8∗2+0gcd(16,2)
所以gcd(1180,482)=2
算法进一步描述如下: 由此得出下面的伪代码:
Euclid(a, b) //求最大公因子
X←a; Y←b;
if Y=0 then return X=gcd(a,b);
R=X mod Y;
X=Y;
Y=R;
goto 3。
扩展的Euclid算法先求出gcd(a, b),当gcd(a, b)=1时,则返回b的逆元。
扩展的Euclid算法
考虑到,根据前面欧几里得算法计算最大公因子的过程: 12345=1∗11111+1234(1234=12345−1∗11111)11111=9∗1234+5(5=11111−9∗1234)1234=246∗5+4(4=1234−246∗5)5=1∗4+1(1=5−1∗4)4=4∗1+0
反向得出: 1=5−1∗4=5−1∗(1234−246∗5)=247∗5−1∗1234=247∗(11111−9∗1234)−1∗1234=247∗11111−2224∗1234=247∗11111−2224∗(12345−1∗11111)=2471∗11111−2224∗12345
得到:11111−1mod 12345=2471
12345−1mod 11111=−2224mod 11111=8887
扩展欧几里德算法的基本形式 扩展的欧几里得算法伪代码描述:
Extended EUCLID(a,b):
(X1,X2,X3) ←(1,0,b);(Y1,Y2,Y3)←(0,1,a)
如果Y3=0 返回; 此时 X3=gcd(a,b);无逆元
如果Y3=1 返回; 此时 Y3=gcd(a,b);Y2即为模b下a的乘法逆元
Q=max_int(X3/Y3)
(T1,T2,T3) ←(X1-Q*Y1,X2-Q*Y2,X3-Q*Y3)
(X1,X2,X3) ←(Y1,Y2,Y3)
(Y1,Y2,Y3) ←(T1,T2,T3)
回到 3
编程要求
根据提示,补全右侧编辑器中 Begin-End 区间的代码,根据输入的 a 和 b,使用扩展欧几里得定理求解 a 关于 b 的逆。具体要求如下:
- 从后台获取两个数字 a 和 b,计算并输出 a 关于 b 的最小正整数逆(保证 gcd(a,b)=1)。
测试说明
平台会对你编写的代码进行测试:
测试输入:2 3
预期输出: 2
测试输入:28 75
预期输出: 67
开始你的任务吧,祝你成功!
/* E_eculid.c
* Copyright 2020 yxm>
*/
#include <stdio.h>
//ÔÚÏÂÃæBeginºÍEndÖ®¼ä²¹È«´úÂ룬¶ÔÊäÈëµÄÁ½¸öÕûÊýa,bÇóa mod bµÄ³Ë·¨ÄæÔª
/*********** Begin ***********/
int ex_gcd(int a,int b,int *x,int *y){
if(b==0){
*x=1;
*y=0;
return a;
}
else{
int result=ex_gcd(b,a%b,x,y);
int k=(*x);
(*x)=(*y);
(*y)=k-(a/b*(*y));
return result;
}
}
int e_Eculid(int a,int b){
int x,y;
x=0,y=0;
int result=ex_gcd(a,b,&x,&y);
if(result==1){
return (x+b)%b;
}
else{
return 0;
}
}
/*********** End ***********/
int main(int argc, char **argv)
{
int a,b;
int x;
scanf("%d%d",&a,&b);
x=e_Eculid(a,b);
if(x==0) printf("ûÓг˷¨ÄæÔª\n");
else printf("%d",x);
return 0;
}
第3关:素性检验
任务描述
本关任务:编写一个能判断输入的十进制整数是否素数的小程序。
相关知识
为了完成本关任务,你需要掌握:如何进行素性检验
素性测试
素性测试主要使用两个定理: 很不幸这个定理并不充分 所以我们再补充一个定理来加强它。 我们现在有了数学公式,但是问题并没有解决,因为p可能非常大,枚举所有的a或x都是不可能的。
于是我们开始猜,我们可以任取一个a并对ap−1
测试,而且,我们计算ap−1
应该使用O(logp)
的算法,即第一关的快速指数运算,这个算法中有平方的步骤,于是我们可以将二次探测放在这一步骤中,以节省时间。
算法返回false时,整数n一定是合数。而当算法返回值为true时,整数n在高概率意义下是素数。可能存在合数n,对于随机选取的基数a,算法返回true。
如何检验一个数是否素数
任取一个a (0<a<n),求an−1mod n
。根据费马小定理,若n为素数,结果应该是1。为了更准确,在过程中嵌入二次探测,返回n的素性测试结果。
Witness(a,n)
for i=k downto 0 do
{
x←d;
d←(d×d) mod n;
if d=1 and(x≠1)and(x≠n-1)then return False;
if bi=1 then d←(d×a) mod n
}
if d≠1 then return False;
return True.
编程要求
根据提示,在右侧编辑器补充代码,利用费马小定理和二次检测定理判断输入的数是否素数。
测试说明
平台会对你编写的代码进行测试:
测试输入:4
; 预期输出: 0
测试输入:5
; 预期输出: 1
开始你的任务吧,祝你成功!
/*
* witness.c
*
* Copyright 2020 yxm
* 利用费马小定理和二次测试,对输入的正整数n进行素性检测
*/
//在下面Begin和End之间补全代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int ksm(int a, int b, int n){
int res = 1;
while(b){
if(b&1){
res = res * a;
res = res % n;
}
a = a * a;
a = a % n;
b = b >> 1;
}
return res;
}
int witness(int a,int n)
{
/*********** Begin ***********/
int flag=ksm(a,n-1,n);
if(flag==1){
for(int x=2;x<n;x++){
if(x*x%n==1){
return 1;
}
}
return 0;
}
return 0;
/*********** End ***********/
}
int main(int argc, char **argv)
{
int a,n,d;
scanf("%d",&n);
srand((unsigned)time(NULL));
//生成(1,n)范围内的随机数a
/*********** Begin ***********/
a=rand()%n+1;
/*********** End ***********/
d=witness(a,n);
printf("%d",d);
return 0;
}