数论

基本数

本文会记录基本数论知识以及相应的代码

整除

整除的定义:设 a,b \in Z(整数) ,a\neq 0。如果 \exists q \in Z, 使得 b=aq,那么就说 b 可被a 整除,记作a | b ,且称 ba 的倍数,ab 的约数(因数)。

b 不被a 整除记作 。

素数

一、素数的判定

1.暴力做法

很容易发现这样一个事实:如果 xa 的约数,那么 \frac{a}{x} 也是a 的约数。

这个结论告诉我们,对于每一对 (x,\frac{a}{x})只需要检验其中的一个就好了。为了方便起见,我们之考察每一对里面小的那个数。不难发现,所有这些较小数就是 [1,\sqrt{a}]这个区间里的数。

bool isprime(int a)
{
   if(a<2) return false;
   for(int i = 2; i  <= a/i; i++){   //(1)
       if(a%i==0) return false;
  }
   return true;
}

【代码说明】:(1)处,我们使用i<=a/i 而不使用i*i<=a 因为 i*i是有可能爆int的

2.素性测试

素性测试(Primality test)是一类在 不对给定数字进行素数分解(prime factorization)的情况下,测试其是否为素数的算法。

这部分内容,现在可以不学,22.3.13用不到

二、算数基本定理(唯一分解定理)

算术基本定理可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积,形式如下:

$$
N=p_1^{a_1}\times p_2^{a_2}\times ...\times p_n^{a_n};
$$

 

其中p_1<p_2<...<p_n\ 且均为素数,其中所有指数a_i为正整数。

有了(1)式之后,我们可以求出 N的约数个数 以及 N的所有约数之和

约数个数:\prod_{i=1}^{n}(a_i+1)

约数之和:\prod_{i=1}^{n}(\sum_{j=0}^{a_i}p_i^j) = (p_1^0+p_1^1+..+p_1^{a_1})\times ...\times (p_n^0+p_n^1+..+p_n^{a_n})

 

程序实现分解质因数

求出 X=p_1^{a_1}\times p_2^{a_2}\times ...\times p_k^{a_k} 中的所有 p 以及对应的 a 。
  1. 试除法分解质因数

int p[N],a[N]; //分别存放pi,ai
int k=0;
void divide(int x)
{
   for(int i = 2; i <= x/i; i++){
       if(x % i==0){
           int count = 0; //用于计算ai
           while(x % i ==0) x/=i,s++;
           p[k]=i;
           a[k++] = count;
      }
  }
   if(x>1) p[k]=x,a[k++]=1;
}
  1. 线性筛法分解质因数

    先线性筛法求所有素数,再用求出素数分解处质因数

    这种方法比较适用于多组测试数据时使用。

    int allp[N],cnt=0;  //这里意义同上
    bool st[N];      //st[i]存储i是否被筛选过
    //先得到所有的p,注意这里的n应为X所能取得最大值
    void get_allp(int n)  
    {
       for(int i = 2; i <= x; i++)
      {
           if(!st[i]) allp[cnt++] = i;
           for(int j = 0; allp[j]<=n/i; j++)
          {
               st[allp[j]*i] = true;
               if(i%p[j]==0) break;
          }
      }
    }

    int p[N],a[N],k=0;
    void divide(int x)
    {
       for(int i = 0;allp[i]<=x;i++){
           if(x%allp[i]==0)
          {
               p[k]=allp[i];
               while(x%allp[i]==0) x/=allp[i],a[k]++;
               
          }
      }
    }

     

 

最大公约数

最大公约数即为 Greatest Common Divisor,常缩写为 gcd。

一组整数的公约数,是指同时是这组数中每一个数的约数的数。 \pm1是任意一组整数的公约数。

一组整数的最大公约数,是指所有公约数里面最大的一个。

下面我们考虑两个数的情况,来求其最大公约数?

特别的:0和任意一个数的最大公约数都是其本身。即gcd(0,b)=b

欧几里得算法

又称为辗转相除法。

如果我们已知两个数 ab ,如何求出二者的最大公约数呢?

不妨设 a>b

我们发现如果ba 的约数,那么 b就是二者的最大公约数。 下面讨论不能整除的情况,即a=b\times q+r ,其中 r<b

我们通过证明可以得到 gcd(a,b)=gcd(b,a\mod\ b),过程如下:

https://oi-wiki.org/math/number-theory/gcd/

int gcd(int a, int b)
{
   if(!b) return a;
   return gcd(b, a % b);
}

欧几里得算法的时间复杂度为O(log n)

拓展欧几里得算法

扩展欧几里得算法(Extended Euclidean algorithm, EXGCD),常用于求 ax+by=gcd(a,b) 的一组可行解。其中x,y为整数。

下面程序实现求解方程ax+by=gcd(a,b) 的解 x,y 并且放回gcd(a,b)

//递归实现,速度稍慢
int Exgcd(int a, int b, int &x, int &y) {
 if (!b) {
   x = 1,y = 0;
   return a;
}
 int d = Exgcd(b, a % b, x, y);
 int t = x;
 x = y;
 y = t - (a / b) * y;
 return d;
}
//递归写法二
int exgcd(int a, int b, int &x, int &y)
{
   if(!b)
  {
       x = 1, y = 0;
       return a;
  }
   int d = exgcd(b, a % b, y , x);
   y -= a / b * x;
   return d;
}
//迭代实现,速度会比递归快
int gcd(int a, int b, int& x, int& y) {
 x = 1, y = 0;
 int x1 = 0, y1 = 1, a1 = a, b1 = b;
 while (b1) {
   int q = a1 / b1;
   tie(x, x1) = make_tuple(x1, x - q * x1);
   tie(y, y1) = make_tuple(y1, y - q * y1);
   tie(a1, b1) = make_tuple(b1, a1 - q * b1);
}
 return a1;
}

数学证明过程如下:最大公约数 - OI Wiki (oi-wiki.org)

设:

ax_1+by_1=gcd(a,b)

bx_2+(a \mod\ b)y_2=gcd(b,a \mod\ b)

由欧几里得定理可知:gcd(a,b)=gcd(b,a \mod\ b)

所以 ax_1+by_1=bx_2+(a \mod\ b)y_2

又因为 a \mod\ b=a-(\lfloor\frac{a}{b}\rfloor) 。。。。。。。。。。。

 

拓展欧几里得算法应用

$$
应用有:\left\{ \begin{matrix} 求解不定方程\\ 求解模线性方程(线性同余方程)\\ 求解模的逆元 \end{matrix} \right.
$$

 

 

  1. 使用拓展欧几里得算法解决不定方程的办法

    对于不定整数方程 ax+by=c,若\ c \mod\ gcd(a,b) =0,则该方程存在整数解,否则无整数解。

    我们先求出方程ax+by=gcd(a,b) 的一组解 x_0 与 y_0,那么其他所有的整数解:

    $$
    x=x_0+\frac{k*b}{gcd},\ \ y=y_0-\frac{k*a}{gcd} \ \ \ (k \in Z)
    $$

而对于方程:ax+by=c 来说,我们只要将x 和y 同时乘以 \frac{c}{gcd} 即可。

所以原方程的一组解为

$$
x_1=x_0*\frac{c}{gcd};\ \ \ y_1=y_0*\frac{c}{gcd}
$$

 

所以其他整数解为:

$$
x=x_1+\frac{k*b}{gcd}, \ \ \ y=y_1\frac{k*a}{gcd} \ \ \ (k \in Z)
$$

 

(5)即为方程的所有整数解。

  1.  

  2. 欧几里得算法求模的逆元

    同余方程ax≡b (mod n),如果 gcd(a,n)== 1,则方程只有唯一解。

    b=1 时,同余方程求出的 x a的对模n 乘法的逆元。 即解方程ax+ny=1\ \ (x,y \in Z)

 

不常用:辗转相减法

利用

$$
gcd(a,b)=gcd(a,b-a)来求出 gcd(a,b)。
$$

 

int gcd_sub(int a, int b)
{
   if(a == b) return a;
   if(a > b) a -= b;
   else b -= a;
   return gcd_s(a, b);
}

题目【辅导课--数论--5-1223.最大比例】

变形的相减法:

int gcd_sub(int a, int b)
{
   if(a < b) swap(a, b);
   if(b == 1) return a;
   return gcd_sub(b, a / b);
}

 

posted @ 2022-03-22 21:51  H~MoRen  阅读(135)  评论(0)    收藏  举报