时间限制:1000ms
单点时限:10000ms
内存限制:256MB
描述
给定三个正整数 a、 b 和 p,满足 b 和 p 互质。这时分数 a / b 除以 p 的余数,即 a / b MOD p 可以定义为 a × b-1 MOD p。
其中b-1 是 b 的逆元,它满足 1 ≤ b-1 < p 且 b × b-1 ≡ 1 MOD p,满足上述条件的 b-1有且仅有一个。
例如 2-1 ≡ 4 MOD 7,因为2 × 4 ≡ 1 MOD 7; 3-1 ≡ 3 MOD 8,因为3 × 3 ≡ 1 MOD 8。
于是我们可以利用b-1求出a / b MOD p,例如: 3 / 2 MOD 7 = 3 × 2-1 MOD 7 = 3 × 4 MOD 7 = 5
给定三个正整数 a、 b 和 p,满足 b 和 p 互质,求 a / b MOD p。
输入
第一行包含3个正整数,a、b 和 p 满足 b 和 p 互质。
对于30%的数据,1 ≤ a, b < p ≤ 100
对于80%的数据,1 ≤ a, b < p ≤ 100000
对于100%的数据,1 ≤ a, b < p ≤ 1000001333
输出
输出 a / b MOD p的值。
样例输入
3 2 7
样例输出
5
其实不过是一个求逆元的题目,但是学到了不少东西,认识到许多学习的东西都是在表面,学的不够扎实。
如果 求逆元的方法可以使用费马小定理,p是质数,那么a关于p的逆元就是a p-2 MOD p(只知道但不知道为什么),可以使用快速幂取模快速求得。
当然,是数据太弱了吧,测试的p居然全部都属素数。
虽然AC但是错误的代码:
#include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> using namespace std; const int INF = 0x3f3f3f3f; typedef long long LL; LL quick_exp(LL a, LL n, int mod) { LL ans = 1; while(n) { if(n & 1) ans = (ans * a) % mod; a = (a * a) % mod; n >>= 1; } return (ans + mod) % mod; } int main() { LL a,b; int p; scanf("%lld%lld%d", &a, &b, &p); int bn = quick_exp(b, p - 2, p); printf("%lld\n", (a * bn) % p); return 0; }
但是,如果p与a是互质的,但是p不是怎么办呢?如果还是利用费马小定理是不够的了。
有个欧拉定理如果p与a互质,那么a φ(p)-1 MOD p = 1. φ(p) 是指比p小的但是与p互质的整数个数。
可以看出来费马小定理是个特例,如果p是素数,那么φ(p)=p-1,自然解决问题。
如果 p = p0k ( p0 是素数) ,那么明显就有φ(p) = p0k-p0k-1 ,提取公因式就有φ(p) = (p0-1)*p0k-1
如果p = p1*p2 ( p1, p2 是素数),那么明显也有φ(p) =φ(p1) *φ(p2)
又因为任意的整数都可以分解成若干素数的乘积(例如 15 = 3 * 5, 120 = 23 * 3 *5),(唯一分解定理)
综上,所以有欧拉函数的计算公式φ(p) = p / p1 * (p1 - 1) / p2 * (p2 - 1) ……
故正确的AC代码应该是
#include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <math.h> using namespace std; const int INF = 0x3f3f3f3f; typedef long long LL; LL euler_phi(LL x) { LL ans = x; int m =sqrt(x); for(int i = 2; i <= m; i++) { if(x % i == 0) { while(x % i == 0) x /= i; ans = ans / i * (i - 1); } } if(x > 1) ans = ans / x * (x - 1); return ans; } LL quick_exp(LL a, LL n, int mod) { LL ans = 1; while(n) { if(n & 1) ans = (ans * a) % mod; a = (a * a) % mod; n >>= 1; } return (ans + mod) % mod; } int main() { LL a,b; int p; scanf("%lld%lld%d", &a, &b, &p); int bn = quick_exp(b, euler_phi(p) - 1, p); printf("%lld\n", (a * bn) % p); return 0; }
当然,我们也可以直接使用扩展欧几里得算法直接计算逆元,而且没有那么多弯弯绕。
#include <iostream> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <math.h> using namespace std; const int INF = 0x3f3f3f3f; typedef long long LL; void extra_gcd(LL a, LL b, LL &x, LL &y, LL &d) { if(b == 0) { d = a; x = 1; y = 0; } else { extra_gcd(b, a%b, y, x, d); y -= (a/b) * x; } } int main() { LL a, b, p, x, y, d; scanf("%lld%lld%lld", &a, &b, &p); extra_gcd(b, p, x, y, d); x = (x + p) % p; printf("%lld\n", (a * x) % p); return 0; }