欧拉定理 | 幂取模 | 同余 | 反复平方法 |
一、用欧拉定理手算
欧拉方程
ϕ
(
n
)
\phi(n)
ϕ(n):
欧拉定理和欧拉方程
ϕ
(
n
)
\phi(n)
ϕ(n)可以百度或者
看我之前的博客(有点简陋)
博客1
博客2
二、利用同余的性质迭代
a
b
m
o
d
m
=
a
(
b
m
o
d
m
)
m
o
d
m
ab\ mod\ m=a(b\ mod\ m)mod\ m
ab mod m=a(b mod m)mod m
简要证明:设b=km+c,则b mod m=c上式两边都等于ac mod m
通过上面的式子可以把b不断缩小
代码:
/**
* @Title:f1
* @Description:TODO 通过公式 ab mod m=b*(a mod m)mod m实现幂取模
* @param x 底数
* @param n 指数
* @param m 除数
* @return_type 余数
* @return int
* @throws
*/
public static int f1(int x,int n,int m) {
int ret=1;
for(int i=0;i<n;++i)
ret=(ret*x)%m;
return ret;
}
三、反复平方法
-
x
n
可
以
表
示
成
x
a
0
2
0
×
x
a
1
2
1
×
x
a
2
2
2
×
⋅
⋅
⋅
×
x
a
k
2
k
x^n可以表示成x^{a_02^0}\times x^{a_12^1}\times x^{a_22^2}\times\cdot\cdot\cdot\times x^{a_k2^k}
xn可以表示成xa020×xa121×xa222×⋅⋅⋅×xak2k
其中 { a i = 0 或 1 ∣ i = 0 , 1 , 2 , . . . , k } \{a_i=0或1\ |\ i=0,1,2,...,k\} {ai=0或1 ∣ i=0,1,2,...,k}即把n换成二进制的形式。
然后计算 x n m o d m x^n\ mod\ m xn mod m可以通过分别计算上面k个数除以m的余数的乘积再除以m(由带余除法可以简单推出) - 又因为
x
2
i
+
1
=
(
x
2
i
)
2
x^{2^{i+1}}=(x^{2^i})^2
x2i+1=(x2i)2,
由第二点的 a b m o d m = a ( b m o d m ) m o d m ab\ mod\ m=a(b\ mod\ m)mod\ m ab mod m=a(b mod m)mod m
可知第i+1项除以m的余数可以由第i项除以m的余数推出,这是什么意思呢?(先不管,把 a i 当 1 看 a_i当1看 ai当1看)比如 第i项是6,它除以4的余数是2,那么第i+1项是36,它除以4的余数可以由(2*2)mod 4得到,即0 - 知道这些就可以写代码了,如下:
(代码先把每一项 x 1 ⋅ 2 i x^{1\cdot2^i} x1⋅2i除以m的余数算出来,然后当 a i a_i ai等于1时,就把余数记录下来,不等于1余数是1不需要记录。记录下来的余数按照第二点里的迭代方法,即这些余数相乘的结果除以m就是最终的答案)
/**
* @Title:f2
* @Description:TODO 反复平方法实现幂取模
* @param x 底数
* @param n 指数
* @param m 除数
* @return_type int
* @return 余数
* @throws
*/
public static int f2(int x,int n, int m) {
int ret=1;
/*prev记录当前循环的前一位除以m的余数,
* 比如n=01001,当执行到第三个0时,它的前一位是0,
* prev就是x^2除以m的余数(第二个0对应的二进制是第三位)*/
int prev=x;
while(n>0) {
if((n&1)==1) { //当前位为1
/*思路同f1()方法,ret记录了n的位数到目前为止
* 除以m的余数,比如n是01011001,假设现在到了1001,
* 那就是(二进制)x^(1001)除以m的余数*/
ret=(ret*prev)%m;
}
prev=(prev*prev)%m;
n>>=1;
}
return ret;
}
四、欧拉方法的代码
/**
* @Title:numOfprimes
* @Description:TODO 计算小于n且与n互质的正整数的个数
* @param n
* @return 个数
* @return_type int
* @throws
*/
public static int numOfprimes(int n) {
double ret=n*1.0;
ArrayList<Integer>p = new ArrayList<Integer>(n/2);
int num=0;
for(int i=2;i*i<=n;++i) {
if((n&(i-1))==0) { //n % i==0
p.add(i);
while(n/i==0)
n/=i;
}
}
if(n>1)
p.add(n);
for(int i=0;i<p.size();++i)
ret=ret*(1-1.0/p.get(i));
return (int)ret;
}
/**
* @Title:f3
* @Description:TODO 运用欧拉公式
* @param x 底数
* @param n 指数
* @param m 除数
* @return_type int
* @return 余数
* @throws
*/
public static int f3(int x,int n,int m) {
int num=numOfprimes(m);
if(num<=0)
return -1;
int count=n%num;
return f1(x,count,m);
}
五、完整代码
package modofpower;
import java.util.ArrayList;
/**
* @author 乃乃天外仙
*
*/
public class mod_power {
/**
* @Title:f1
* @Description:TODO 通过公式 ab mod m=b*(a mod m)mod m实现幂取模
* @param x 底数
* @param n 指数
* @param m 除数
* @return_type 余数
* @return int
* @throws
*/
public static int f1(int x,int n,int m) {
int ret=1;
for(int i=0;i<n;++i)
ret=(ret*x)%m;
return ret;
}
/**
* @Title:f2
* @Description:TODO 反复平方法实现幂取模
* @param x 底数
* @param n 指数
* @param m 除数
* @return_type int
* @return 余数
* @throws
*/
public static int f2(int x,int n, int m) {
int ret=1;
/*prev记录当前循环的前一位除以m的余数,
* 比如n=01001,当执行到第三个0时,它的前一位是0,
* prev就是x^2除以m的余数(第二个0对应的二进制是第三位)*/
int prev=x;
while(n>0) {
if((n&1)==1) { //当前位为1
/*思路同f1()方法,ret记录了n的位数到目前为止
* 除以m的余数,比如n是01011001,假设现在到了1001,
* 那就是(二进制)x^(1001)除以m的余数*/
ret=(ret*prev)%m;
}
prev=(prev*prev)%m;
n>>=1;
}
return ret;
}
/**
* @Title:numOfprimes
* @Description:TODO 计算小于n且与n互质的正整数的个数
* @param n
* @return 个数
* @return_type int
* @throws
*/
public static int numOfprimes(int n) {
double ret=n*1.0;
ArrayList<Integer>p = new ArrayList<Integer>(n/2);
int num=0;
for(int i=2;i*i<=n;++i) {
if((n&(i-1))==0) { //n % i==0
p.add(i);
while(n/i==0)
n/=i;
}
}
if(n>1)
p.add(n);
for(int i=0;i<p.size();++i)
ret=ret*(1-1.0/p.get(i));
return (int)ret;
}
/**
* @Title:f3
* @Description:TODO 运用欧拉公式
* @param x 底数
* @param n 指数
* @param m 除数
* @return_type int
* @return 余数
* @throws
*/
public static int f3(int x,int n,int m) {
int num=numOfprimes(m);
if(num<=0)
return -1;
int count=n%num;
return f2(x,count,m);
}
public static void main(String[]args) {
System.out.println("f1()方法结果:"+f1(7,200,19));
System.out.println("f2()方法结果:"+f2(7,200,19));
System.out.println("f3()方法结果:"+f3(7,200,19));
}
}
输出结果:
六、时间复杂度
利用同余性质迭代的算法的时间复杂度是
O
(
n
)
O(n)
O(n)(n是指数)
反复平方法的时间复杂度是
O
(
l
o
g
2
n
)
O(log_2n)
O(log2n)(n是指数)
欧拉方法的时间复杂度是
O
(
m
1
2
)
O(m^{1\over2})
O(m21)(m是底数),因为计算小于m且与m互质的正整数个数需要
O
(
m
1
2
)
O(m^{1\over2})
O(m21)的时间,之后调用f2()的时间只需要
O
(
l
o
g
2
m
)
O(log_2m)
O(log2m),所以总的时间复杂度是
O
(
m
1
2
)
O(m^{1\over2})
O(m21)
七、Reference
1.A Concise Introduction to the Theory of Numbers- Baker A第二章
2.幂取模算法:https://blog.csdn.net/chen77716/article/details/7093600