信息安全数学基础
课程实验报告
实验报告(四)
一、实验内容
/------------1)计算整数a mod p的指数-------/
/------------2) 计算模p的所有原根g----------/
比较算法运行效率。由于在线运行的效率更高,所以此处我直接以第二种类似于查找的算法来计算。
二、实验目标
通过本次实验掌握编程的技巧和整合以前的所学知识,进而简化实现的难度,并降低算法的时间复杂度。
三、实验原理
原根的定义:
定义Ordm(a)为使得a^d≡1(modm)成立的最小的d(其中a和m互质)
由欧拉定理可知: Ord≤Φ(m)
当Ordm(a)=Φ(m)时,称a是模m意义下m的一个原根Ordm(a)=Φ(m)时,称a是模m意义下m的一个原根(记住原根是a,不是d!)
首先我们需要知道原根若存在必定满足以下条件:
① 具有原根的数字仅有以下几种形式:2,4,p^n ,2*p^n(p是奇质数)
② 一个数的最小原根的大小不超过 m^0.25。
③ 若g是m的一个原根,那么g^d是m的原根的充分必要条件是gcd(d,Φ(m))=1。
④ 由此可推知一个数的原根个数为Φ(Φ(m))个
四、实验设计
在实现过程中,我加入了自己实现的gcd函数,复杂度约为O(n>>1)。
其次是get_prime函数,该函数的目的是再筛选欧拉函数的时候同时做到把其中的素数也筛选出来。
Judge函数用于对原根是否存在作判断。复杂度O(t << m);
Pow( )函数用于对a ^ m mod m 进行计算。复杂度为O(log(2)m)
Check() 函数用于选择出其中最小的原根。复杂度为O(m)。
Find_zhi()函数用于筛选出Phi【i】欧拉函数的约数,同时计算出其中最小的原根。复杂度为O(m)
Slove()函数用于对其中所有的原根进行筛选,并打印。复杂度约为O(m)。
Mod()函数打印出阶数。
Input_Begin()用于对所求的数进行一个循环流输入。
Int main() 用于对各个函数平衡调用。
Coding
/*------------1)计算整数a mod p的指数-------*/
/*------------2) 计算模p的所有原根g----------*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2.1e6;
const int N = 3e6+10;
int is_pri[maxn],Prime[maxn];
int phi[maxn],st[maxn];//int phi[]
int top;
int tot;
int g;
int ans[maxn];
/*-----------------gcd 求最大公因数-------------*/
inline int gcd(int a,int b)
{
if(b) while((a%=b) && (b%=a));
return a+b;
}
/*----------------1.筛出质数并进行第一步(顺便把欧拉函数也筛出来):------*/
void get_prime()
{
is_pri[1]=1;
for(register int i=2;i<N;i++){
if(!is_pri[i]) Prime[++tot]=i,phi[i]=i-1;
for(register int j=1;j<=tot;j++){
if(1ll*i*Prime[j]>=N) break;
register int res=i*Prime[j];
is_pri[res]=1;
if(i%Prime[j]==0){
phi[res]=phi[i]*Prime[j];
break;
}
phi[res]=phi[i]*phi[Prime[j]];
}
}
}
bool judge(int m)//判断原根有无
{
if(m==1) return 0;
if(m==2||m==4) return 1;
if((m&1)==0) m>>=1;if((m&1)==0) return 0;
for(int i=2;i<=tot&&(1ll*Prime[i]*Prime[i]<=m);i++)
{
if(m%Prime[i]!=0) continue;
while(m%Prime[i]==0) m/=Prime[i];
if(m==1) return 1;
return 0;
}
return m;
//这里要return m, 由于Prime[i]*Prime[i]>m即退出的影响
}
int Pow(int x,int n,int mod)
{
int ans=1;
while(n){
if(n&1) ans=(1ll*ans*x)%mod;
x=(1ll*x*x)%mod;
n>>=1;
}
return ans;
}
/*------------2.找出最小原根:----------*/
bool check(int x,int m)
{
if(Pow(x,phi[m],m)!=1) return 0;
for(int i=1;i<=top;i++)if(Pow(x,st[i],m)==1) return 0;
return 1;
}
void Find_ZHI(int m){
for(int i=2;1ll*i*i<=phi[m];i++){
//筛出phi的约数,用于check
if(phi[m]%i==0){
st[++top]=i;
if(i*i!=phi[m]) st[++top]=phi[m]/i;
}
}
for(g=2;g<=100;g++)//枚举最小原根
{ //由于 小于 m^0.25 故可选范围为 1-1e8
if(check(g,m))
{
cout << g << endl;
break;
}
}
}
/*------------------找出所有原根--------------------*/
void slove(int m){
int cnt=0;
register int res=1;
for(register int i=1;i<=phi[m];i++)
//由欧拉定理,只用枚举到g^phi[m]
{
if(cnt==phi[phi[m]]) break;//个数限制
res=(1ll*res*g)%m;
if(gcd(i,phi[m])!=1) continue;
ans[++cnt]=res;
}
sort(ans+1,ans+1+cnt);//由于取了模,要sort
for(register int i=1;i<=cnt;i++)
{
if(i>1) putchar(' ');
printf("%d",ans[i]);
}
puts("");
}
int Mod(int a,int m){
for(int i = 1;i<=phi[m];i++){
if(Pow(a,i,m)==1){
return i;
}
}
}
void Input_Begin(){
cout << "\t\t1 :Problem one(a --- m)。 2:Problem two(--m--)\t\t" <<endl;
int choose; cin >> choose;
int a,m;
get_prime(); //先存表。
while(choose){
if(choose==1){
cout <<"\t\t-------------请输两个数:-------------\t\t"<<endl;
cin>>a>>m;
cout <<a <<"mod"<<m <<"的指数为:"<< Mod(a,m)<< endl;
}
else if(choose==2){
cout <<"\t\t-------------请输一个数:-------------\t\t"<<endl;
cin >> m;
cout <<"最小的原根为:"<<endl;
Find_ZHI(m);
cout <<"所有的原根为:"<<endl;
slove(m);
}
cout << "\t\t 0:exit()。1:Problem one(a --- m)。 2:Problem two(--m--)。 \t\t" <<endl;
cin >> choose;
if(choose==0){
cout <<"\t\tSuccessfully Sloved\t\t"<<endl;
}
}
}
signed main(){
ios::sync_with_stdio(false);
cout.tie(0);
cin.tie(0);
Input_Begin();
return 0;
}
五、实验结果
最后调用结束:
六、总结
通过本次实验,自己对原根的理解更为深刻了一些。通过求解欧拉函数,快速幂,GCD辗转相除法,这些都是前面所学的知识,一一进行实现,再加入原根的知识,再根据分析每一步的复杂度均不超过O(m)所以所用的总时间也是接近O(m)的,只是在实现过程中,我先用了表格的方法来存储Φ(m) 和Prime【】函数。这样在查找时均为O(m)的复杂度,所以总的时间复杂度就是O(m + k) ;k 为提前预定的一个数,这里设置为3e6+10也就是说在此范围内筛选。
信安应怜和你一起努力