1.模运算基本公式 和 几点注意
/*
(a + b) mod n = ((a mod n) + (b mod n)) mod n
(a - b) mod n = ((a mod n) - (b mod n) + n) mod n
(a * b) mod n = ((a mod n) * (b mod n)) mod n
对于除法没有类似公式,但是可以通过求逆元的方法,转换成乘法运算求模(见下面)
*/
*注意:a是负数时a%m的结果也是负数 此时改为a%m+m就能保证结果在0~m-1范围内
注意防范中间结果溢出危险
当(a*c) mod p == (b*c) mod p时 (a-b)*c可以被p整除
利用上边这个公式 可以实现对大整数的取模 (因为1234 = ((1*10+2)*10+3)*10+4 )
2.求逆元
方法1:O(log max(a, m))
/*
扩展欧几里得求 a 关于 模m 逆元
这个办法通用 但是若gcd(a, m) != 1逆元就是不存在的
*/
int extgcd(int a, int b, int &x, int &y){
int d = a;
if(b != 0){
d = extgcd(b, a%b, y, x);
y -= (a / b) * x;
}
else{
x = 1; y = 0;
}
return d;
}
int mod_inverse(int a, int m){
int x, y;
extgcd(a, m, x, y);
return (m + x%m) % m;
}
方法2:O(1)
这个方法求逆元需要用到下面讲到的 快速幂取模 运算,直接调用就好啦
插入一个定理
/*
费马小定理:
有a和p,如果p是质数,那么: a^(p - 1) % p = 1;
*/
/*
如果 模p 是个素数, 那求逆元就变得很简单啦
有费马小定理可得:
a 关于 p 的逆元 aa = a^(p - 2) % p
这是由于a^(p-1) % p = 1,等同于a * (a^(p-2)) % p = 1,所以a^(p-2)%p为逆元
*/
3.带模除法(逆元) O(log max(a, m)) 或 O(1)
这里介绍扩展欧几里得算法实现的求逆元 如果 模p 是个素数 则完全可以使用 费马小定理求逆元
/*
(a / b) mod p == (a * bb) mod p
bb是b关于p的逆元 bb = mod_inverse(b, p);
*/
int extgcd(int a, int b, int &x, int &y){
int d = a;
if(b != 0){
d = extgcd(b, a%b, y, x);
y -= (a / b) * x;
}
else{
x = 1; y = 0;
}
return d;
}
int mod_inverse(int a, int m){
int x, y;
extgcd(a, m, x, y);
return (m + x%m) % m;
}
int main(){
int a, b, p;
while(scanf("%d%d%d",&a, &b, &p) != EOF){
printf("1: %d\n", (a/b) % p); //正常除法取模
int tmp = mod_inverse(b, p);
printf("2: %d\n", (a*tmp) % p); //逆元除法取模
}
return 0;
}
4. 快速幂取模运算(反复平方法)
/*
当n为偶数时 x^n = ((x^2)^(n/2)), 递归转化为n/2的情况
当n为奇数时 x^n = ((x^2)^(n/2))*x, 同样也递归转为n/2的情况
不断递归下去
*/
long long mod_pow(long long x, long long n, long long mod){
if(n == 0) return 1;
long long res = mod_pow(x*x%mod, n/2, mod);
if(n&1) res = res * x % mod;
return res;
}