数论基础

数论基础

数论

线性筛素数

洛谷P3383 【模板】线性筛素数

数据范围:n=10^8,找出第k 小的质数

欧拉线性筛的关键在于:每个合数只被它最大的非自身的因数筛掉:最大的非自身的因数*最小质因子=这个数

例如:12不会被4筛掉,而是被6筛掉;45不会被9筛掉,而是被15筛掉。

i的最小质因子一定是prime[j],因为如果小于j,一定会在更早被枚举到时一定会break掉,就不存在现在这一步

i*prime[j]的最小质因子也是prime[j],与上一步同理

(对break情况的进一步说明)当出现i的最小质因子prime[j]时,

​ 我们就知道n*prime[j](n>i) 的形式一定会在后面再次出现,也就是说这个数的最大因数还没有出现,我们留到后面再把他筛掉,及时break掉保证每个数只被筛一次

​ 通俗易懂的举例:以12为例,2,3,4,6都可筛掉12

​ i=2:2 * 2=4 break; i=3 3 * 2=6, 3 * 3=9 break;

​ i=4: 4 * 2 =8 break; i=5 5 * 2=10, 5 * 3=15; 5 * 5=25 break;

​ i=6: 6 * 2=12 break;实现了在12的最大因数6 才将12筛掉

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
32
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e8+5;
int a[N],prime[N/3];
int cnt=0;
int n,q,op;
void init()
{
for(int i=2;i<=n;i++)
{
if(!a[i])prime[++cnt]=i; //如果之前没筛掉,那么这个数成为下一个质数
for(int j=1;i*prime[j]<=n and j<=cnt;j++)
{
a[i*prime[j]]=1;//质数的倍数都是合数,筛去
if(i%prime[j]==0)break;
//线筛的关键一步
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>q;
init();
while(q--)
{
cin>>op;
cout<<prime[op]<<endl;
}
return 0;
}

求约数个数

image-20210219140422202

acwing 870

给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对10^9+7取模。

输入格式

第一行包含整数n。

接下来n行,每行包含一个整数ai。

输出格式

输出一个整数,表示所给正整数的乘积的约数个数,答案需对10^9+7取模。

数据范围

1≤n≤100
1≤ai≤2∗10^9

输入样例:

1
2
3
4
3
2
6
8

输出样例:

1
12

代码:

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
32
33
34
#include<iostream>
#include<cstdio>
#include<cmath>
#include<unordered_map>
using namespace std;
const int m=1e9+7;
int n,x;
int a[10050];
int cnt=0;
int main()
{
cin>>n;
unordered_map<int,int> prime;
while(n--)
{
cin>>x;
for(int i=2;i<=x/i;i++)
{
while(x%i==0)
{
x/=i;
prime[i]++;
}
}
if(x>1)prime[x]++;
}
long long res=1;
for(auto i:prime)
{
res=res*(i.second+1)%m;
}
cout<<res;
return 0;
}

求约数之和

image-20210219141117464

acwing 871

给定n个正整数ai,请你输出这些数的乘积的约数之和,答案对10^9+7取模。

输入格式

第一行包含整数n。

接下来n行,每行包含一个整数ai。

输出格式

输出一个整数,表示所给正整数的乘积的约数之和,答案需对10^9+7取模。

数据范围

1≤n≤100,
1≤ai≤2∗10^9

输入样例:

1
2
3
4
3
2
6
8

输出样例:

1
252

代码:

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
32
33
34
35
36
37
38
39
40
#include<iostream>
#include<cstdio>
#include<cmath>
#include<unordered_map>
using namespace std;
const int m=1e9+7;
int n,x;
int a[10050];
int cnt=0;
int main()
{
cin>>n;
unordered_map<int,int> prime;
while(n--)
{
cin>>x;
for(int i=2;i<=x/i;i++)
{
while(x%i==0)
{
x/=i;
prime[i]++;
}
}
if(x>1)prime[x]++;
}
long long res=1;
for(auto i:prime)
{
int p=i.first,a=i.second;
long long t=1;
while(a--)
{
t=(t*p+1)%m;
}
res=res*t%m;
}
cout<<res;
return 0;
}

最大公约数

欧几里得算法:辗转相除法

d|a and d|b —-> d|ax+by

gcd(a,b)=gcd(b,a-c*b)=gcd(b,a%b);

代码:

1
2
3
4
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}

欧拉函数

定义

在数论,对正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目(因此φ(1)=1)

算数基本定理

算术基本定理又叫唯一分解定理,可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积N=P1^a1^ P2^a2^P3^a3^……Pn^an^,这里P1<P2<P3……<Pn均为质数,其中指数ai是正整数。

image-20210219152636559

acwing873 欧拉函数

给定n个正整数ai,请你求出每个数的欧拉函数。

1≤n≤100,
1≤ai≤2∗10^9

输入样例:

1
2
3
4
3
3
6
8

输出样例:

1
2
3
2
2
4

代码:

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
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int main()
{
cin>>n;
while(n--)
{
int x;
cin>>x;
int res=x;
for(int i=2;i<=x/i;i++)
{
if(x%i==0)
{
res=res/i*(i-1);
while(x%i==0)x/=i;
}
}
if(x>1)res=res/x*(x-1);
cout<<res<<endl;
}
return 0;
}

acwing874 筛法求欧拉函数

给定一个正整数n,求1~n中每个数的欧拉函数之和。1≤n≤10^6

解析:欧拉函数的筛法是线性筛素数的加强版

第一种解释:

要想求欧拉函数需要用到以下几个性质( p为素数 ):

  1. phi(p) == p-1 因为素数p除了1以外的因子只有p,所以与 p 互素的个数是 p - 1个

  2. phi(p^k) == p^k - p^(k-1) == (p-1) * p^(k-1)

    证明:

    令n == p^k,小于 n 的正整数共有 p^k-1 个,其中与 p 不互素的个数共 p^(k-1)-1 个,它们是 1p,2p,3*p … (p^(k-1)-1)*p

    所以phi(p^k) == (p^k-1) - (p^(k-1)-1) == p^k - p^(k-1) == (p-1) * p^(k-1)。

  3. 如果i mod p == 0, 那么 phi(i * p) == p * phi(i) (证明略)

    举个例子:

    假设 p = 3,i = 6,p * i = 18 = 2 * 3^2;

    phi(3 * 6) == 18*(1-1/2)*(1-1/3) = 6

    p * phi(i) = 3 * phi(6) = 3 * 6 * (1-1/2) * (1-1/3) = 6 = phi(i * p) 正确

  4. 如果i mod p != 0, 那么 phi(i * p) == phi(i) * (p-1)

    证明:

    i mod p 不为0且p为质数, 所以i与p互质, 那么根据积性函数的性质 phi(i * p) == phi(i) * phi(p) 其中phi(p) == p-1

    所以 phi(i * p) == phi(i) * (p-1).

    再举个例子:

    假设i = 4, p = 3, i * p = 3 * 4 = 12

    phi(12) = 12 * (1-1/2) * (1-1/3) = 4

    phi(i) * (p-1) = phi(4) * (3-1) = 4 * (1-1/2) * 2 = 4 = phi(i * p)正确

第二种解释:

phi[prime[j] * i]分为两种情况:

  1. i % prime[j] == 0时:primes[j]是i的最小质因子,也是prime[j] * i的最小质因子,因此1 - 1 / prime[j]这一项在phi[i]中计算过了,只需将基数N修正为prime[j]倍,最终结果为phi[i] * prime[j]。
  2. i % primes[j] != 0时:primes[j]不是i的质因子,只是prime[j] * i的最小质因子,因此不仅需要将基数N修正为prime[j]倍,还需要补上1 - 1 / prime[j]这一项,因此最终结果phi[i] * (prime[j] - 1)。

代码:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e6+5;
int a[N],prime[N/3];
int phi[N];
long long tot=0,cnt=0;
int n,q,op;
void init()
{
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])
{
prime[++cnt]=i;
phi[i]=i-1;//如果i是质数,显然有i-1个数与它互质
//cout<<"phi["<<i<<"]="<<phi[i]<<endl;
}
for(int j=1;i*prime[j]<=n and j<=cnt;j++)
{
a[i*prime[j]]=1;//质数的倍数都是合数,筛去
if(i%prime[j]==0)
{
phi[prime[j]*i]=phi[i]*prime[j];
//cout<<"phi["<<prime[j]*i<<"]="<<phi[prime[j]*i]<<endl;
break;
}//说明prime[j]是i的一个质因子,那么prime[j]*i与i的所有质因子都相同
phi[prime[j]*i]=phi[i]*(prime[j]-1);
//cout<<"phi["<<prime[j]*i<<"]="<<phi[prime[j]*i]<<endl;
}
}
for(int i=1;i<=n;i++)
{
tot+=phi[i];
}
}
int main()
{
ios::sync_with_stdio(0);
cin>>n;
init();
cout<<tot;
return 0;
}

欧拉定理

内容:

​ 若a与n互质,则有a^phi(n) ≡ 1(mod p)。

证明:

image-20210220110900952

费马小定理(欧拉定理的特例):

如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p)。

快速幂

基于 二进制 位运算 来实现

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
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll qpow(ll a,ll b,ll p)
{
ll res=1;
while(b)
{
if(b&1)//当前最低二进制位为1就乘进去
{
res=res*a%p;
}
a=a*a%p;
b>>=1;//指数右移一位
}
return res%p;
}
int main()
{
ios::sync_with_stdio(0);
int n;
ll a,b,p;
cin>>n;
while(n--)
{
cin>>a>>b>>p;
cout<<qpow(a,b,p)<<endl;
}
return 0;
}

乘法逆元

参考:https://www.cnblogs.com/zjp-shadow/p/7773566.html

逆元的定义

image-20210220113622905

image-20210220113352798

求法总结

  1. 拓展欧几里得:解线性同余方程,不要求模数为质数
  2. 快速幂:费马小定理,要求模数为质数
  3. 线性求逆元,求一连串数字对一个质数p的逆元,线性递推,要求模数为质数

快速幂求逆元

原理:

image-20210220114738010

代码:

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
32
33
34
35
36
37
38
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll qpow(ll a,ll b,ll p)
{
ll res=1;
while(b)
{
if(b&1)//当前位为1就乘进去
{
res=res*a%p;
}
a=a*a%p;
b>>=1;//指数右移一位
}
return res%p;
}
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
ios::sync_with_stdio(0);
int n;
ll a,b,p;
cin>>n;
while(n--)
{
cin>>a>>p;
ll res=qpow(a,p-2,p);
if(gcd(a,p)==1)cout<<res<<endl;
else cout<<"impossible"<<endl;
//当且仅当a,p不互质时无解
}
return 0;
}

扩展欧几里得算法

引理:裴蜀定理

若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。

它的一个重要推论是:a,b互质的充分必要条件是存在整数x,y使ax+by=1

一些自己的理解:

a*x mod n==1 <=> (ax-1)整除n <=> 令y=-(ax-1)/n

<=> ax-ny==1 目标转化为求方程的解x,y(x的解为x0+kn)

求方程的解:ax+by=c,c一定是gcd(a,b)的倍数,否则无整数解 (裴蜀定理)

ax+by=gcd(a,b)=1 –> 联想到gcd(a,b)的求法:gcd(a,b)=gcd(b,a%b); (1式)

有:bx1+(a%b)y1=gcd(b,a%b)=1 (2式)

由 1式==2式 –> ax+by=bx1+(a-(a/b))y1

​ –> ax+by=ay1+b(x1-(a/b)y1)

​ –>x=y1,y=x1-(a/b)y1 得到递推关系,可以设计递归函数

求x,y转化为求x1,y1

——why? ——由gcd(a,b)的边界条件为 b=0 可知递归边界一定是 ax+0y==gcd(a,0), 随便给一组合法解为x=1,y=0

从上往下推:求gcd ; 从下往上推:求最初二元一次不定方程的解!

扩欧求乘法逆元代码:

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
32
33
34
35
36
37
38
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
ll x,y;
void exgcd(ll a,ll b)
{
if(b==0)
{
x=1,y=0;
return ;
}
exgcd(b,a%b);
ll x0=x,y0=y;
x=y0;
y=x0-(a/b)*y0;
//求解方程的递推关系写在后面,从递归边界时的条件xn=1,yn=0开始往前推出x,y
return ;
}
int main()
{
ll a,n;
while(cin>>a>>n)
{
if(a==0 and n==0)break;
exgcd(a,n);
while(x<0)
{
x+=n;
}
x%=n;
//以上令x为最小正整数解
cout<<x<<endl;
}
return 0;
}

acwing877 扩展欧几里得算法

给定n对正整数ai,bi,对于每对数,求出一组xi,yi,使其满足ai∗xi+bi∗yi=gcd(ai,bi)。本题答案不唯一,输出任意满足条件的xi,yi均可。

代码:

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
32
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int n;
ll x,y;
void exgcd(ll a,ll b)
{
if(b==0)
{
x=1,y=0;
return ;
}
exgcd(b,a%b);
ll x1=x,y1=y;
x=y1;
y=x1-y1*(a/b);
return ;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n;
ll a,b;
while(n--)
{
cin>>a>>b;
exgcd(a,b);
cout<<x<<" "<<y<<endl;
}
return 0;
}

acwing878 线性同余方程

给定n组数据ai,bi,mi,对于每组数求出一个xi,使其满足ai∗xi≡bi(mod mi),如果无解则输出impossible。

代码:

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
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int n;
ll x,y;
ll d;
void exgcd(ll a,ll b)
{
if(b==0)
{
x=1,y=0;
return ;
}
exgcd(b,a%b);
ll x1=x,y1=y;
x=y1;
y=x1-y1*(a/b);
return ;
}
ll gcd(ll a,ll b)
{
return b==0?a:gcd(b,a%b);
}
int main()
{
ios::sync_with_stdio(0);
cin>>n;
ll a,b,m;
while(n--)
{
cin>>a>>b>>m;
d=gcd(a,m);
if(b%d==0)
{
exgcd(a,m);//转化为ax-my=b;
cout<<x*(b/d)%m<<endl;//结果要扩大k=b/d倍
}
else cout<<"impossible"<<endl;
}
return 0;
}

中国剩余定理

内容

image-20210220143613917