[数论第二节]欧拉函数/快速幂/扩展欧几里得算法

  • 欧拉函数

    • 欧拉函数φ(N) : 1-N中与N互质的数的个数
    • N=p1a1·p2a2·p3a3····pnan 其中p为N的所有质因子
    • φ(N)=N(11p1)(11p2)···(11pn)
    • 证明:

      • 互质:两数的公共因子只有1
      • 去掉所有与N有(大于1的)公共因子的数,剩下的数就是与N互质的数
      • 对N的所有质因子pk,去掉所有pk(与N有公共因子的数),个数为 Npk,即NNp1Np2···Npn
      • 对于数 pi·pj,在去掉 pi 的倍数和去掉 pj 的倍数的过程中去除了两次,所以要加上一次,即N(Np1+Np2+···+Npn)+(Np1p2+Np1p3+···+Npn1pn)
      • 对于数 pi·pj·pk,在去掉pk的倍数的过程中被去掉了三次,在加上合数 pi·pj 的过程中被加上了三次,所以合数 pi·pj·pk 没有被去掉,因此要去掉它,即N(Np1Np2···Npn)+(Np1p2+Np1p3+···+Npn1pn)(Np1p2p3+Np1p2p4+···+Npn2pn1pn)
      • 对于合数 pi·pj·pk·pm 同理,归纳递推可知,所有质数个数为:Ni=1nNpi+1<=i<j<=nNpipj1<=i<j<k<=nNpipjpk+···+(1)2n11<=i<j<k<···<=nNpipjpk···p
      • 同样基于容斥原理:
        • |ABC|=|A|+|B|+|C||AB||AC||BC|+|ABC|

        • |i=1nAi|=i|Ai|i,j|AiAj|++(1)n+1|i=1nAi|

        • 与N有(大于1的)公共因子的个数 =| pi 的倍数的个数| - |pipj 的倍数的个数| + |pipjpk 的倍数的个数 ··· (1)n+1 |pipj···pn 的倍数的个数|
      • 将上式因式分解后:N(11p1)(11p2)···(11pn)
    • 容斥原理:

      int res = a;
      for(int i = 2; i <= a / i; ++ i){
          if(a % i == 0){//i为质因子
              res = res / i * (i - 1);//套公式
              while(a % i == 0) a /= i;//把因子除干净
          }
      }
      if(a > 1) res = res / a * (a - 1);//最后一个因子可能大于sqrt(a)
      
    • 筛选法:

      • 利用线性筛选质数的过程求出每个数的欧拉函数
      • 欧拉函数为积性函数,当a与b互质时有φ(a·b)=φ(a)·φ(b)
      • i为质数时,φ(i)=i1
      • ipj=0时,pj为i的质因子,此时pj·i的质因子与i的质因子完全相同,所以 φ(pj·i)=pj·i·(11p1)(11p2)···(11pn)=pj·φ(i)
      • ipj0时,pj不为i的质因子,此时pj·i的质因子比i的质因子多一个pj,所以 φ(pj·i)=pj·i·(11p1)(11p2)···(11pn)(11pj)=pj·φ(i)·(11pj)=(pj1)·φ(i)
      • 代码:
        const int N = 1e6 + 10;
        typedef long long LL;
        int primes[N], cnt;//质数数组,下标
        int st[N];//标记为合数
        int phi[N];//欧拉函数
        int n;
        
        LL get_eulers(int n){
            phi[1] = 1;
            for(int i = 2; i <= n; ++ i){
                if(!st[i]){
                    primes[ ++ cnt] = i;
                    phi[i] = i - 1;//质数i的欧拉函数为i - 1
                }
                for(int j = 1; primes[j] <= n / i; ++ j){
                    int pj = primes[j];
                    st[pj * i] = 1;
                    if(i % pj == 0){//i是合数
                        phi[pj * i] = pj * phi[i];//pj为i的质因子
                        break;
                    }
                    else phi[pj * i] = (pj - 1) * phi[i];//pj不是i的因子
                }
            }
            
            LL ans = 0;
            for(int i = 1; i <= n; ++ i) ans += phi[i];
            return ans;
        }
        
    • 欧拉函数的应用

      • 欧拉定理

        • a与n互质,则aφ(n)(modn)1
        • 证明:
          • 若a与b互质,且a(modb)0a(modb) 也与b互质
          • a1,a2,a3···aφ(n) 为1~n中的所有与n互质的数
          • 每个数同时乘上a得a·a1,a·a2,a·a3···a·aφ(n) 由于a也与n互质,所以乘a后所得的数也全部与n互质
          • 将每个数mod n,取模后的每个数都在1~n范围内,并且仍然都与n互质,显然取模后的这几个数就是原来的几个数,只不过数的顺序可能发生了变化
          • 将所有数相乘,则有a·a1·a·a2·a·a3···a·aφ(n)(modn)=a1·a2·a3···aφ(n)
          • 所以有:aφ(n)·(a1a2a3···aφ(n))(modn)(a1·a2·a3···aφ(n))aφ(n)(modn)1
          • 特别地,当n为质数时,an1(modn)1 为费马定理
  • 快速幂

    • 快速地求出ak(modb),时间复杂度为O(log(k))
    • 将k拆分为二进制相加,即
    • k=c1·21+c2·22+c3·23+···+cn·2n

    • 其中 ci 是k的二进制的每一位(0/1),n最大为k的二进制的位数
    • 所以
    • ak(modb)=ac1·21+c2·22+c3·23+···+cn·2n(modb)=ac1·21·ac2·22·ac3·23···acn·2n(modb)

    • 所以每次遍历k的所有二进制位,同时预处理出ai,根据k的二进制的取值将答案累乘
    • 代码:
      typedef long long LL;
      //求a^k mod b
      int qmi(int a, int k, int b){
      	int res = 1;
      	while(k){
      		if(k & 1) res = (LL)res * a % b;//k的当前位非0,则将a累乘到答案
      		k >>= 1;//k右移一位
      		a = (LL)a * a % b;//a的幂倍增
      	}
      	return res;
      }
      
    • 快速幂求逆元

      • b与p互质,且 b|aab=a·x(modp) ,则x为b的逆元
      • 两边乘b有a=a·b·x(modp)
      • 所以有b·x=1(modp)
      • 若p是质数,有费马定理bp1=1(modp)x=bp2
      • 若p不是质数,有欧拉定理bφ(p)=1(modp)x=bφ(p)1
      • 代码:
        int qmi(int a, int b, int p){
        	略...
        }
        if(b与q互质){ //互质是有解的前提
        	if(p为质数) cout << qmi(b, p - 2, p);
        	else cout << qmi(b, phi[p] - 1, p);
        }else 无解
        
  • 扩展欧几里得定理

    • 裴蜀定理:对于任意正整数a,b, 都一定存在整数x,y, 使得ax+by=gcd(a,b), 并且 gcd(a,b) 一定是a与b能构造出来的最小公约数

    • 扩展欧几里得算法就是求这样的x,y

    • 用于求解方程 ax+by=gcd(a,b) 的解

    • b=0ax+by=a 故而 x=1,y=0

    • b0 时,因为

    • gcd(a,b)=gcd(b,a%b)

    • b·x1+(a%b)·y1=gcd(b,a%b)

    • b·x1+(aa/b·b)·y1=gcd(b,a%b)

    • ay1+b·(x1a/b)=gcd(b,a%b)=gcd(a,b)

    • 故而 x=y1,y=x1a/b·y1

    • 代码:

      int exgcd(int a, int b, int &x, int &y){
          if(!b){ //b = 0时,ax + by = gcd(a, 0) = a,所以x = 1, y = 0;
              x = 1, y = 0;
              return a;
          }else {
              int d = exgcd(b, a % b, x, y);//交换a与b,x与y
              int t = x;
              x = y;//x1 = y2
              y = t - a / b * y;//y1 = x2 - a / b * y2
              return d;
          }
      }
      
      //简化版
      void exgcd(int a, int b, int &x, int &y){
          if(!b){
              x = 1, y = 0;
              return a;
          }else{
      	    int d = exgcd(b, a % b, y, x);
      	    y -= a / b * x;
      	    return d;
          }    
      }
      
    • 扩展欧几里得求解线性同余方程

      • 线性同余方程:axb(modm) 其中a,x,b,m均为整数,x为未知数,方程等价于 ax=km+b,也等价于 ax+mk=b其中x,k均为未知数
      • 方程形如:ax+by=c 的直线方程,解(x,y)为直线上散列的点
      • 求出a,b的最大公约数d=gcd(a,b),方程化为 d(xad+ybd)=c易知其中 xad+ybd 为整数,所以要求 d|c,故 c=k·d=k·gcd(a,b)
      • 所以线性同余方程axb(modm)有解的充要条件为b为gcd(a,m)的倍数
      • bk·gcd(a,m) 时(k 为整数),方程无解
      • b=k·gcd(a,m) 时,先用扩展欧几里得求出方程 ax+mk=gcd(a,m) 的特解(x,k),可知 ax+mk=b 方程的解为 bgcd(a,m)·(x,k) ,所以原式的解为 bgcd(a,m)·x
      • 代码:
        const int N = 1e5 + 10;
        typedef long long LL;
        int n; 
        int a, b, m;
        int x, y;
        //扩展欧几里得算法
        int exgcd(int a, int b , int &x, int &y){
            if(!b){
                x = 1, y = 0;
                return a;
            }else{
                int t = exgcd(b, a % b, y, x);
                y -= a / b * x;
                return t;
            }
        }
        
        int main(){
            cin >> n;
            while(n -- ){
                cin >> a >> b >> m;
                int t = exgcd(a, m, x, y);
                //b是gcd(a,m)的倍数才有解
                if(b % t != 0) cout << "impossible" << endl;
                else cout << (LL)x * (b / t) % m << endl;//特解乘上倍数
            }
            return 0;
        }
        
  • 中国剩余定理

    • 中国剩余定理 (Chinese Remainder Theorem, CRT) 可求解如下形式的一元线性同余方程组(其中 n1,n2,n3,···nk 两两互质):
    • {xa1(modn1)xa2(modn2)xak(modnk)

    • 过程:
      • 计算所有模数的积
      • 对于第 i 个方程:
        • 计算 mi=nni
        • 计算 mi 在模 ni 意义下的逆元 mi1
        • 计算 ci=mimi1 (不要对 ni 取模)
      • 方程组在模 n 意义下的唯一解为:x=i=1kaici(modn)
    • 扩展版中国剩余定理:

      • 不满足 n1,n2,n3,···nk 两两互质时,求解线性同余方程组
      • xa1(modn1)x=k1n1+a1

      • xa2(modn2)x=k2n2+a2

      • 由①②知k1n1+a1=k2n2+a2
      • 移项k1n1k2n2=a2a1
      • 其中 n1,n2,a2,a1 已知,k1,k2 未知,可以用扩展欧几里得算法求出
      • d=a2a1gcd(n1,n2)0 时无解
      • d=a2a1gcd(n1,n2)=0 时,先求出方程
      • k1n1k2n2=gcd(n1,n2)

      • 的特解,求出 k1,k2
      • k1=k1·d,k2=k2·d

      • 此时 k1,k2 就是原方程的特解
      • 观察原方程可知通解为
      • k1=k1+k·n2d,k2=k2+k·n1d(k)

      • 将通解代入①式得
      • x=(k1+k·n2d)n1+a1=(k1n1)+a1+k·n1n2d=(k1n1)+a1+k·lcm(n1,n2)

      • 所以
      • x=k·lcm(n1,n2)+(k1n1)+a1

      • 其中 k1,n1,a1,lcm(n1,n2) 已知,k 未知
      • n2=lcm(n1,n2),a2=(k1n1)+a1 所以③式化为
      • x=k·n2+a2

      • 所以将①②式合并后的③式仍与方程组方程相似,于是可以循环将方程组合并为一个式子,通过最后一个式子即可用扩展欧几里得算法求出x
      • 代码:
        const int N = 30;
        typedef long long LL;
        int n;
        
        LL exgcd(LL a, LL b, LL &x, LL &y){
            if(!b){
                x = 1, y = 0;
                return a;
            }
            LL d = exgcd(b, a % b, y, x);
            y -= a / b * x;
            return d;
        }
        int main(){
            cin >> n;
            LL a1, m1;
            bool flag = true;
            cin >> a1 >> m1;//用于存储更新的a与m
            
            for(int i = 1; i < n; ++ i){ //合并n-1次
                LL an, mn;//接受新的a与m
                LL k1, k2; //存储用欧几里得求的系数
                cin >> an >> mn;
                LL d = exgcd(a1, an, k1, k2); //求出gcd(a1, an)以及系数k1, k2
                if((mn - m1) % d){ //无解判断
                    flag = false;
                    break;
                }
                LL t = an / d; //通解k1 = k1 + k * (an / d), k2 = k2 + k * (a1 / d),题目为了求最小的x,故要让k1为最小解故要k1不断膜上t
                k1 = k1 * (mn - m1) / d; //更新k1
                k1 = (k1 % t + t) % t; //取k1的最小解
                m1 = k1 * a1 + m1; //m更新为k1 * a1 * m1
                a1 = abs(a1 / d * an);  //a1更新为gcd(a1, an)
            }
            
            if(flag){
                cout << (m1 % a1 + a1) % a1; //最后一个式子的就是余数m
            }else cout << -1;
            return 0;
        }
        

posted on   MoiLip  阅读(123)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示