数论基础知识

一、最大公约数gcd

约数和倍数的定义(百度百科)

整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数。
显然,任何非0整数是0的约数,0不是任何数的约数。

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

二、最小公倍数lcm

定理:lcm(a,b)gcd(a,b)=ab

int lcm(int a,int b)
{
    return a*b/gcd(a,b);
}

三、质数相关结论

1.一个正整数n,最多只有一个大于根号n的质因数(基本应用是质因数分解)

2.一个10^9以内的数,最多有9个不用的质因子(此定理往往和容斥原理结合使用)
(23571113171923)

3.素数定理

从不大于n的自然数随机选一个,它是素数的概率大约是1/ln n

lim π(x)/x = 1/lnx
x→∞

π(x)表示小于等于x的素数的个数

四、质数筛选法

  1. 埃拉托斯特尼筛法

要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。
步骤
详细列出算法如下:
列出2以后的所有序列:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
标出序列中的第一个素数,也就是2,序列变成:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
将剩下序列中,划掉2的倍数,序列变成:
2 3 5 7 9 11 13 15 17 19 21 23 25
如果现在这个序列中最大数小于最后一个标出的素数的平方,那么剩下的序列中所有的数都是素数,否则回到第二步。
本例中,因为25大于2的平方,我们返回第二步:
剩下的序列中第一个素数是3,将主序列中3的倍数划掉,主序列变成:
2 3 5 7 11 13 17 19 23 25
我们得到的素数有:2,3
25仍然大于3的平方,所以我们还要返回第二步:
现在序列中第一个素数是5,同样将序列中5的倍数划掉,主序列成了:
2 3 5 7 11 13 17 19 23
我们得到的素数有:2,3,5 。
因为23小于5的平方,跳出循环.
结论:2到25之间的素数是:2 3 5 7 11 13 17 19 23。

代码实现:

#include
#include
#include
#define ll long long

using namespace std;
const ll maxn=1e7+5;
ll prime[maxn];
bool vis[maxn];//1代表质数,0代表非质数

void sieve(ll n)
{
    ll m=(ll)sqrt(n+0.5);
    memset(vis,0,sizeof(vis));
    for(ll i=3;i<=m;i+=2)
    {
        if(!vis[i])
            for(ll j=i*i;j<=n;j+=i)
                vis[j]=1;
        if(i*i>n)
            break;
    }
}

ll gen(ll n)
{
    sieve(n);
    ll c=1;
    prime[0]=2;
    for(ll i=3;i<=n;i+=2)
        if(!vis[i])
            prime[c++]=i;
    return c;
}

int main()
{
    ll n,c;
    cin>>n;
    c=gen(n);
    for(ll i=0;i

2.欧拉筛法

首先,先明确一个条件,任何合数都能表示成一系列素数的积。
然后利用了每个合数必有一个最小素因子,每个合数仅被它的最小素因子筛去正好一次。所以为线性时间。
代码中体现在:
if(i%prime[j]==0)break;
prime数组 中的素数是递增的,当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉。
因为i中含有prime[j], prime[j] 比 prime[j+1] 小。接下去的素数同理。所以不用筛下去了。
在满足i%prme[j]==0这个条件之前以及第一次满足改条件时,prime[j]必定是prime[j]*i的最小因子。
如果还不是很理解,可以手动模拟如下:

prime[0]=2;
vis[4]=1;
prime[1]=3;
vis[6]=1;
vis[9]=1;
vis[8]=1;
prime[2]=5;
vis[10]=1;
vis[15]=1;
vis[25]=1;
vis[12]=1;
prime[3]=7;
vis[14]=1;
vis[21]=1;
vis[16]=1;
vis[18]=1;
vis[20]=1;
prime[4]=11;
vis[22]=1;
vis[24]=1;
prime[5]=13;
prime[6]=17;
prime[7]=19;
prime[8]=23;


#include
#include
#define ll long long
using namespace std;

const ll maxn=1e7+5;
int prime[maxn];
bool vis[maxn];

ll sieve(ll n)
{
    ll cnt=0;
    memset(vis,0,sizeof(vis));
    for(ll i=2;i>n;
    cout<

五、分解质因数

两个没有共同质因子的正整数称为互质。因为1没有质因子,1与任何正整数(包括1本身)都是互质。正整数的因数分解可将正整数表示为一连串的质因子相乘,质因子如重复可以指数表示。根据算术基本定理,任何正整数皆有独一无二的质因子分解式。只有一个质因子的正整数为质数。

1没有质因子。
5只有1个质因子,5本身。(5是质数。)
6的质因子是2和3。(6 = 2 × 3)
2、4、8、16等只有1个质因子:2(2是质数,4 = 2,8 = 2,如此类推。)
10有2个质因子:2和5。(10 = 2 × 5)

就是一个数的约数,并且是质数,比如8=2×2×2,2就是8的质因数。12=2×2×3,2和3就是12的质因数。把一个式子以12=2×2×3的形式表示,叫做分解质因数。16=2×2×2×2,2就是16的质因数,把一个合数写成几个质数相乘的形式表示,这也是分解质因数。[1]
分解质因数的方法是先用一个合数的最小质因数去除这个合数,得出的数若是一个质数,就写成这个合数相乘形式;若是一个合数就继续按原来的方法,直至最后是一个质数 。

试除法是整数分解算法中最简单和最容易理解的算法。
给定一个合数n(这里,n是待分解的整数),试除法看成是用小于等于的每个素数去试除待分解的整数。如果找到一个数能够整除除尽,这个数就是待分解整数的因子。

运用试除算法求1233的因数。

1233=3^2*137.

代码实现:

#include
#include
#define ll long long
using namespace std;

int main()
{
    ll n;
    cin>>n;
    vector >v;
    vector >::iterator it;
    for(ll i=2;i*i<=n;i+=(i==2?1:2))
    {
        if(n%i==0)
        {
            int cnt=0;
            while(n%i==0){cnt++;n/=i;}
            v.push_back(make_pair(i,cnt));
        }
    }
    if(n!=1)v.push_back(make_pair(n,1));
    for(it=v.begin();it!=v.end();it++)
        cout<first<<" "<second<

【例题】 求2到5e6的所有数所拥有的质因数的个数,然后,给出两个正整数a,b,a>b>1,求a!/b!这个数所拥有的所有质因数的个数。
代码如下(用cin,cout会超时):

#include
#include
#include
#define ll long long

using namespace std;

const ll maxn=5e6+5;
ll cnt[maxn];
bool vis[maxn];

void init()
{
    memset(cnt,0,sizeof(cnt));
    memset(vis,true,sizeof(vis));
    for(ll i=2;i

六、同余

  1. 定义
    给定正整数M,用M去除两个整数a,b,如果余数相同,则称a,b对模M同余,记作:a≡b(mod M)
  2. 性质
    自反性:a≡a(mod M)
    对称性:若a≡b(mod M),则b≡a(mod M)
    传递性:若a≡b(mod M),b≡c(mod M),则a≡c(mod M)
  3. 加减乘运算
    若a≡b(mod M)则有:
    a+c≡b+c(mod M)
    a-c≡b-c(mod M)
    ac≡bc(mod M)
  4. 除法运算
    若a≡b(mod M),c为非0整数并且gcd(c,M)=1,则有:
    ainv[c]≡binv[c](mod M)
    其中,inv[c]是c对模M的逆元,满足c*inv[c]≡1(mod M)

七、整数二元一次不定方程(扩展欧几里得求解)

形如ax+by=c(a,b均不为0)的方程,a,b,c都是整数,求(x,y)整数解。

  • 判断是否有解

整数二元一次方程有解的充要条件是gcd(a,b)|c。如果不能整除则无解。

  • 扩展欧几里得求特解

欧几里得给出了计算ax+by=gcd(a,b)的解法
代码如下:

#define ll long long

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0){x=1,y=0;return a;}
    ll d=exgcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-a/b*y;
    //此处特别注意不可写成y=tmp-a*y/b,要先除后乘,防止中间结果溢出
    return d;
}

上述递归函数中x,y变化情况推导:
由欧几里德算法gcd(a,b)=gcd(b,a%b),所以ax1+by1=gcd(a,b)=gcd(b,a%b)=bx2+(a%b)y2,现在只要做一些变形就可以得到扩展欧几里德算法中的用到的式子了。令k=a/b(商),r=a%b(余数),那么a=kb+r。所以r=a-kb,带入上式,得到ax1+by1=bx2+(a-(a/b)b)y2=ay2+b(x2-(a/b)y2) => x1=y2,y1=x2-(a/b)y2。有了这两个式子我们就知道了在用欧几里德求最大公约数的时候,相应的参数x,y的变化。现在再回过头来看一下扩展欧几里德算法的代码就很好理解了,实际上扩展欧几里德就是在求a和b的最大公约数的同时,也将满足方程ax1+by1=gcd(a,b)的一组x1和y1的值求了出来。

  • 通解

ax+by=gcd(a,b)在有解并求出特解(x,y)的情况下,通解可以表示为
x’=x+b/gcd(a,b)t;
y’=y-a/gcd(a,b)t;
对于一般式a
x+b
y=c,如果有解,只需要把ax+by=gcd(a,b)的特解乘上c/gcd(a,b)即可得到其特解,通解还是一样的公式。

  • 实例
#include
#define ll long long

using namespace std;

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0){x=1,y=0;return a;}
    ll d=exgcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-a/b*y;
    return d;
}

int main()
{
    ll a,b,c,x,y,d;
    cin>>a>>b>>c;
    d=exgcd(a,b,x,y);
    if(c%d!=0)cout<<"no solution"<

八、欧拉函数

  • 费马小定理

假如p是质数,且gcd(a,p)=1,那么 a^(p-1)≡1(mod p)

  • 欧拉函数定义

在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目(φ(1)=1)。例如φ(8)=4,因为1,3,5,7均和8互质。

  • 通式

这里写图片描述

其中p1, p2……pn为x的所有质因数,x是不为0的整数。

  • 不完全积性

在正整数定义域,若a,b互质,且有f(a*b)=f(a)f(b),则称f(n)是一个积性函数(或不完全积性函数)。
若对任意a,b,均有f(a
b)=f(a)f(b),则称f(n)是一个完全积性函数。
常见的不完全积性函数有欧拉函数,若有n=a
b且gcd(a,b)=1,则有φ(n)=φ(a)*φ(b)。

  • 筛法求欧拉函数

先看常见的公式如下(以下所有字母都是正整数,且p是质数):

  1. φ§=p-1
  2. 若n=pk,则φ(n)=(p-1)*p(k-1)
  3. 若n=p*a,
    if not p, φ(n)=(p-1)φ(a);
    if p|a, φ(n)=p
    φ(a);

下面介绍欧拉筛法求解欧拉函数
【例题 The Euler function HDU - 2824 】
The Euler function phi is an important kind of function in number theory, (n) represents the amount of the numbers which are smaller than n and coprime to n, and this function has a lot of beautiful characteristics. Here comes a very easy question: suppose you are given a, b, try to calculate (a)+ (a+1)+…+ (b)

Input
There are several test cases. Each line has two integers a, b (2

不妨设欧拉函数是f(n),下面求解欧拉函数的主要依据如下三点内容:
1.如果n是质数,则有f(n)=n-1;
2.如若m%n=0,则有f(mn)=f(m)n;
3.若果m%n!=0,则有f(m
n)=f(m)
(n-1);

AC代码:

#include
#include
#define ll long long

using namespace std;
const int maxn=3000000+5;
ll phi[maxn];
bool vis[maxn];
int prime[maxn];

//欧拉筛法求欧拉函数,并计算前n项和
void init_sieve()
{
    memset(vis,0,sizeof(vis));
    int cnt=0;
    for(int i=2;i>a>>b)
    {
        cout<

九、逆元

  • 定义

如果ab≡1(mod M),那么a和b互为对模M的逆元。
a/b mod M <=> a
inv[b] mod M

  • 逆元的求法

求a(mod M)意义下的逆元,要求a与M互质,否则不存在乘法逆元

扩展欧几里得法
设a的逆元是x,则
ax≡1(mod M)可化为等式ax+My=1(推导方法是,该等式两边同时模M,结果就是ax≡1(mod M))

欧拉函数法
数论基础知识_第1张图片

特别的,当M是质数时,根据费马小定理,a^(M-2)就是a对模M的一个逆元。

  • O(n)预处理法
如果M是质数,那么显然1到M-1都有对模M的逆元,假设n
证明:
令M=k*i+r(0<=rr≡ -k*i(mod M)
模等号两边同时乘r的逆元
<=>
r*inv[r]≡ -k*inv[r]*i(mod M)
<=>
1≡ -k*inv[r]*i(mod M)
<=>
1≡ (M-k)*inv[r]*i(mod M)
由逆元的定义知,(M-k)*inv[r]就是i的逆元。

你可能感兴趣的:(紫书算法学习记)