1. 程式人生 > >HihoCoder 1153 分數取模

HihoCoder 1153 分數取模

了吧 利用 phi %d hiho 單點 但是 輸入 nbsp

時間限制:1000ms

單點時限:10000ms

內存限制:256MB

描述

給定三個正整數 abp,滿足 bp 互質。這時分數 a / b 除以 p 的余數,即 a / b MOD p 可以定義為 a × b-1 MOD p

其中b-1b 的逆元,它滿足 1 ≤ b-1 < pb × 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

給定三個正整數 abp,滿足 bp 互質,求 a / b MOD p

輸入

第一行包含3個正整數,abp 滿足 bp 互質。

對於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;
}

HihoCoder 1153 分數取模