zoukankan      html  css  js  c++  java
  • 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
    ", (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
    ", (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
    ", (a * x) % p);
        return 0;
    }
    
    
    如果有错误,请指出,谢谢
  • 相关阅读:
    is 和 == 的区别,utf和gbk的转换,join用法
    python字典的整理信息
    poj分类
    cloud computing
    POJ1007 关于STL排序方法 动态数组的创建 和向量的使用
    math类
    研究生考试感想
    4.11
    重看设计模式 观察者模式
    子串计算 2010北京大学复试机试题
  • 原文地址:https://www.cnblogs.com/Alruddy/p/7191388.html
Copyright © 2011-2022 走看看