HihoCoder 1153 分数取模

时间限制: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;
}

posted @ 2017-07-16 18:56  可达龙  阅读(3661)  评论(0编辑  收藏  举报