zoukankan      html  css  js  c++  java
  • ACM数论之旅6---数论倒数,又称逆元(我整个人都倒了( ̄﹏ ̄))

    数论倒数,又称逆元(因为我说习惯逆元了,下面我都说逆元)

    数论中的倒数是有特别的意义滴

    你以为a的倒数在数论中还是1/a吗

    (・∀・)哼哼~天真

    先来引入求余概念

    (a +  b) % p = (a%p +  b%p) %p  (对)

    (a  -  b) % p = (a%p  -  b%p) %p  (对)

    (a  *  b) % p = (a%p *  b%p) %p  (对)

    (a  /  b) % p = (a%p  /  b%p) %p  (错)

    为什么除法错的

    证明是对的难,证明错的只要举一个反例

    (100/50)%20 = 2       ≠      (100%20) / (50%20) %20 = 0

    对于一些题目,我们必须在中间过程中进行求余,否则数字太大,电脑存不下,那如果这个算式中出现除法,我们是不是对这个算式就无法计算了呢?

    答案当然是 NO (>o<)

    这时就需要逆元了

    我们知道

    如果

    a*x = 1

    那么x是a的倒数,x = 1/a

    但是a如果不是1,那么x就是小数

    那数论中,大部分情况都有求余,所以现在问题变了

    a*x  = 1 (mod p)

    那么x一定等于1/a吗

    不一定

    所以这时候,我们就把x看成a的倒数,只不过加了一个求余条件,所以x叫做    a关于p的逆元

    比如2 * 3 % 5 = 1,那么3就是2关于5的逆元,或者说2和3关于5互为逆元

    这里3的效果是不是跟1/2的效果一样,所以才叫数论倒数

    a的逆元,我们用inv(a)来表示

    那么(a  /  b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p

    这样就把除法,完全转换为乘法了 (。・ω・),乘法超容易

    正篇开始

    逆元怎么求

    (忘了说,a和p互质,a才有关于p的逆元)

    方法一:

    费马曾经说过:不想当数学家的数学家不是好数学家(( ̄▽ ̄)~*我随便说的,别当真)

    费马小定理

    a^(p-1) ≡1 (mod p)

    两边同除以a

    a^(p-2) ≡1/a (mod p)

    什么(,,• ₃ •,,),这可是数论,还敢写1/a

    应该写a^(p-2) ≡ inv(a) (mod p)

    所以inv(a) = a^(p-2) (mod p)

    这个用快速幂求一下,复杂度O(logn)(ง •̀_•́)ง 

     1 LL pow_mod(LL a, LL b, LL p){//a的b次方求余p 
     2     LL ret = 1;
     3     while(b){
     4         if(b & 1) ret = (ret * a) % p;
     5         a = (a * a) % p;
     6         b >>= 1;
     7     }
     8     return ret;
     9 }
    10 LL Fermat(LL a, LL p){//费马求a关于b的逆元 
    11         return pow_mod(a, p-2, p);
    12 }

    方法二:

    要用扩展欧几里德算法

    还记得扩展欧几里德吗?(不记得的话,欧几里得会伤心的(╭ ̄3 ̄)╭♡)

    a*x + b*y = 1

    如果ab互质,有解

    这个解的x就是a关于b的逆元

    y就是b关于a的逆元

    为什么呢?

    你看,两边同时求余b

    a*x % b + b*y % b = 1 % b

    a*x % b = 1 % b

    a*x = 1 (mod b)

    你看你看,出现了!!!(/≥▽≤/)

    所以x是a关于b的逆元

    反之可证明y

    附上代码:

     1 #include<cstdio>
     2 typedef long long LL;
     3 void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
     4     if (!b) {d = a, x = 1, y = 0;}
     5     else{
     6         ex_gcd(b, a % b, y, x, d);
     7         y -= x * (a / b);
     8     }
     9 }
    10 LL inv(LL t, LL p){//如果不存在,返回-1 
    11     LL d, x, y;
    12     ex_gcd(t, p, x, y, d);
    13     return d == 1 ? (x % p + p) % p : -1;
    14 }
    15 int main(){
    16     LL a, p;
    17     while(~scanf("%lld%lld", &a, &p)){
    18         printf("%lld
    ", inv(a, p));
    19     }
    20 }

    方法三:

    当p是个质数的时候有
    inv(a) = (p - p / a) * inv(p % a) % p

    这为啥是对的咩?

    证明不想看的孩子可以跳过。。。( ̄0  ̄)

    证明:
    设x = p % a,y = p / a
    于是有 x + y * a = p
    (x + y * a) % p = 0
    移项得 x % p = (-y) * a % p
    x * inv(a) % p = (-y) % p
    inv(a) = (p - y) * inv(x) % p
    于是 inv(a) = (p - p / a) * inv(p % a) % p

    然后一直递归到1为止,因为1的逆元就是1

    代码:

     1 #include<cstdio>
     2 typedef long long LL;
     3 LL inv(LL t, LL p) {//求t关于p的逆元,注意:t要小于p,最好传参前先把t%p一下 
     4     return t == 1 ? 1 : (p - p / t) * inv(p % t, p) % p;
     5 }
     6 int main(){
     7     LL a, p;
     8     while(~scanf("%lld%lld", &a, &p)){
     9         printf("%lld
    ", inv(a%p, p));
    10     }
    11 }

    这个方法不限于求单个逆元,比前两个好,它可以在O(n)的复杂度内算出n个数的逆元

    递归就是上面的写法,加一个记忆性递归,就可以了

    递推这么写

     1 #include<cstdio>
     2 const int N = 200000 + 5;
     3 const int MOD = (int)1e9 + 7;
     4 int inv[N];
     5 int init(){
     6     inv[1] = 1;
     7     for(int i = 2; i < N; i ++){
     8         inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
     9     }
    10 }
    11 int main(){
    12     init();
    13 }

    又学到新知识了o(*≧▽≦)ツ好开心

  • 相关阅读:
    UVa 12174 (滑动窗口) Shuffle
    UVa 1607 (二分) Gates
    CodeForces ZeptoLab Code Rush 2015
    HDU 1525 (博弈) Euclid's Game
    HDU 2147 (博弈) kiki's game
    UVa 11093 Just Finish it up
    UVa 10954 (Huffman 优先队列) Add All
    CodeForces Round #298 Div.2
    UVa 12627 (递归 计数 找规律) Erratic Expansion
    UVa 714 (二分) Copying Books
  • 原文地址:https://www.cnblogs.com/linyujun/p/5194184.html
Copyright © 2011-2022 走看看