HihoCoder 1153 分數取模
時間限制: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
給定三個正整數 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
雖然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; }
HihoCoder 1153 分數取模