数论初步

前置知识

1.唯一分解定理

也称算术基本定理,任何一个大于 (1) 的自然数,都可以唯一分解成有限个质数的乘积。即质因数分解。

2.同余

定义:设 (m) 是给定的一个正整数,(a,b) 是整数,若满足 (mmid (a-b)) ,则称 (a)(b) 对模 (m) 同余,记为 (aequiv b(mod m))。这个式子称为 模 (m) 的同余式。

同余概念又常表达为:

1.(a=b+km,(kin Z))

2.(a)(b)(m) 除时有相同的余数。

性质

  1. 同余式可以逐项相加:

(a_1 equiv b_1 (mod m),a_2 equiv b_2 (mod m),a_2 equiv b_2 (mod m),dots,a_n equiv b_n (mod m)),则

[a_1+a_2+dots+a_nequiv (b_1+b_2+dots+b_n)(mod m) ]

  1. 同余式可以移项,需要反号。

(a+c equiv b(mod m)),则

[aequiv b-c(mod m) ]

  1. 同余式可以逐项相乘,类似相加。

  2. 同余式每一边边可以加上或减去模 (m) 的任意倍数。

(aequiv b(mod m)) ,则(apm kmequiv b(mod m))

  1. 同余式两边的数如有公约数,此公约数又和模互素,那么就可以把两边的数除以这个公约数。

(aequiv b(mod m))(a=a_1d,b=b_1d,gcd(m,d)=1), 则(a_1equiv b_1pmod m)

  1. 同余式两边的数和模可以同时乘上一个整数。

(aequiv bpmod m) ,则 (akequiv bkpmod {mk})

  1. 同余式两边的数和模可以同时被它们任一公约数除。

(aequiv bpmod m)(a=a_1d,b=b_1d,m=m_1d),则(a_1equiv b_1pmod {m_1})

  1. 如果同余式对于模m成立,那么它对于m的任意约数相等的模d也成立。

(aequiv bpmod m)(m=m_1d) ,则(aequiv bpmod d)

  1. 如果同余式一边上的数和模能被某个数除尽,则同余式的另一边的数也能被这个数除尽。

(aequiv bpmod m ,amid k,mmid k,) 则,(bmid k)

  1. 同余式一边上的数与模的最大公约数,等于另一边上的数与模的最大公约数。

(aequiv bpmod m),则((a,m)=(b,m))


1.求正整数 (N) 的所有约数和

若正整数 (N) 在唯一分解定理中:

[N=p_{1}^{k_1} cdot p_{2}^{k_2} cdot p_{3}^{k_3}... cdot p_{m}^{k_m} ]

则有约数和:

[sum=prod_{i=1}^m {(sum_{j=1}^{k_i} p_i^j)} ]


2.求正整数 (N) 的约数个数:

若正整数 (N) 在唯一分解定理中:

[N=p_{1}^{k_1} cdot p_{2}^{k_2} cdot p_{3}^{k_3}... cdot p_{m}^{k_m} ]

则其约数个数

[sum= prod_{i=1}^m (k_i+1) ]


3.求出正整数的所有约数

试除法:暴力大法好


void func(int n)//n为要求的数
{
	for(int i=1;i<=n/i;i++)
	{
		if(n%i==0) 
		{
			var[++tot]=i;//用var来存储因数
			if(i!=n/i) var[++tot]=n/i;//因数是成对的、但√(n)两个因数一样
		}
	}
	sort(var+1,var+1+tot);//让因数顺序从小到大
}

4.欧拉函数

欧拉函数(phi (x))是指 (1-n) 中与(n) 互质的数的个数

若在唯一分解定理中,(N=p_{1}^{k_1} cdot p_{2}^{k_2} cdot p_{3}^{k_3}... cdot p_{m}^{k_m}),

[phi (N)=N (1-frac1 p_1)(1-frac1 p_2)......(1-frac1 p_m), (1) ]

容斥原理求法

1.从 (1-N) 中去掉 (p_1,p_2,......p_k) 的所有倍数

[N - frac{N}{p_1} - frac{N}{p_2}-frac{N}{p_3}-...frac{N}{p_k} ]

2.加上所有 (p_i cdot p_j) 的倍数

[+ frac{N}{p_1p_2}+frac{N}{p_2p_3}+... ]

3.减去所有 (p_i cdot p_j cdot p_k) 的倍数

[-frac{N}{p_1p_2p_3}-frac{N}{p_2p_3p_4}-... ]

4.加上所有 (p_i cdot p_j cdot p_k cdot p_l)

[+frac{N}{p_1p_2p_3p_4}+... ]

以此类推(......)

最后得到的式子,恒等变形之后与((1))式相同。

递推(筛法)求欧拉函数:

((1)) 式可得如下关系:当 (p_j)(i) 的一个质因子时

[phi(i imes p_j)=phi(i) imes p_j ]

结合线筛,便可得到以下代码

int n;
int primes[N],tot=0;
bool mp[N];
int phi[N];

long long get_eular(int n)
{
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!mp[i])
		{
			primes[tot++]=i;
			phi[i]=i-1;
		}
		for(int j=0;primes[j]*i<=n;j++)
		{
			mp[primes[j]*i]=1;
			if(i%primes[j]==0)
			{
				phi[primes[j]*i]=phi[i]*primes[j];//核心递推式
				break;
			}
			phi[primes[j]*i]=phi[i]*(primes[j]-1);//核心递推式
		}
	}
	long long sum=0;//求所有数的欧拉函数和
	for(int i=1;i<=n;i++)
		sum+=phi[i];
	return sum;
}


5.欧拉定理

(a)(n) 互质,则 :
(a^{phi(n)}equiv 1(mod n))
(定理5.1)

由定义不难看出,欧拉定理是费马小定理的扩展。

简易证明:

(1sim n)中,与(n)互质的数为(a_1,a_2,a_3,a_4...a_{phi(n)})

(ecause a)(n) 互质

( herefore aa_1,aa_2,aa_3...aa_{phi(n)}) 互质于 (n)

则易得:

[aa_1 cdot aa_2 cdot aa_3 cdot ... cdot aa_{phi(n)}equiv a_1 cdot a_2 cdot a_3 cdot ... a_{phi(n)}(mod n) ]

[a^{phi(n)} cdot (a_1 cdot a_2 cdot ... cdot a_{phi(n)}) equiv a_1 cdot a_2 cdot a_3 cdot ... a_{phi(n)}(mod n) ]

消去得:

[a^{phi(n)}equiv 1(mod n) ]


6.逆元

定义:若对任意正整数(a,b)(frac{a}{b}equiv a cdot x (mod m)),则称 (x)(b) 的模 (m) 逆元,简记为(b^{-1})

变形(b cdot b^{-1}equiv 1(mod m))

可利用扩展欧几里得算法解线性同余方程来求逆元。

(m)为质数时,满足费马小定理:

[b^{m-1}equiv 1(mod m) ]

[Rightarrow b cdot b^{m-2}equiv 1(mod m) ]

即对于任意正整数 (b) ,其模任意不整除 (b) 的质数 (m) 的乘法逆元是 (b^{m-2})(定理6.1)

对于模数为质数的情况,可使用快速幂来求逆元。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll fpow(int a,int k,int p)
{
	int res=1;
	while(k)
	{
		if(k&1) res=(ll)res*a%p;
		k>>=1;
		a=(ll)a*a%p;
	}
	return res;
}

int main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		int a,k,p;
		scanf("%d%d",&a,&p);
		
		int res=fpow(a,p-2,p);
		if(a%p) printf("%d
",res);
		else puts("impossible");//整除
	}
	return 0;
}

7.裴蜀定理

对于任意一对正整数 (a,b) ,一定存在整数 (x,y) 使得 (ax+by=gcd(a,b))。(定理7.1)

构造性证明:扩展欧几里得算法

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;
}

8.求解线性同余方程

对于线性同余方程(axequiv b(mod m))

(exists yin Z,st:ax=my+b)

(Rightarrow ax-my=b);
(y^{prime}=-y)

(ax+my^{prime}=b)

不难看出可以使用扩欧解此方程

((a,b)mid b)时,无解;

((a,b) mid b)时,有解。

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll 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 main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		int a,b,m;//输入a,b,m
		scanf("%d%d%d",&a,&b,&m);
		int x, y;
		x=(x%m+m)%m;//转化为最小正整数解
		int d=exgcd(a,m,x,y);
		if(b%d) puts("impossible");
		else printf("%lld
",(ll)x*(b/d)%m);
	}
	
	return 0;
}

9.高斯消元解线性方程组

对于方程:

[egin{cases} a_{1_1}x_1+a_{1_2}x_2+ cdots +a_{1_n}x_n=b_1\ a_{2_1}x_1+a_{2_2}x_2+ cdots +a_{2_n}x_n=b_2\ vdots\ a_{n_1}x_1+a_{n_2}x_2+ cdots +a_{n_n}x_n=b_n\ end{cases} ]

其系数矩阵为

[egin{bmatrix} a_{1_1}&a_{1_2}&dots&a_{1_n}&b_1\ a_{2_1}&a_{2_2}&dots&a_{2_n}&b_2\ vdots&vdots&&vdots&vdots\ a_{n_1}&a_{n_2}&dots&a_{n_n}&b_n\ end{bmatrix} ]

通过初等行列变换,转换为上三角形式:

[egin{cases} a_{1_1}x_1+a_{1_2}x_2+ cdots +a_{1_n}x_n=b_1\ a_{2_2}x_2+ cdots +a_{2_n}x_n=b_2\ vdots\ x_n=b_n\ end{cases} ]

当转化后为完美阶梯形时,有唯一解

否则:无解(( ext{非0}=0))或有无穷多组解((0=0))

高斯消元步骤:

枚举每一列c:

1.找到绝对值最大的一行,将这一行与顶行交换位置,这行不再移动。

2.等式两边同除,将该行第一个数变为 (1)

3.通过等式之间加减,将当前列下面的所有数消为(0)

重复至最后一列,然后回代求解。

code:

#include <bits/stdc++.h>
using namespace std;

const int N=110;
const double eps=1e-6;//精度限制

int n;
double a[N][N];

int gauss()
{
	int c,r;//c表示在枚举哪一列,r表示在枚举哪一行
	for(c=0,r=0;c<n;c++)
	{
		int t=r;
		for(int i=r;i<n;i++)
		{
			if(fabs(a[i][c])>fabs(a[t][c]))//定位
				t=i;
		}
		
		if(fabs(a[t][c])<eps) continue;
		
		for(int i=c;i<=n;i++)	swap(a[t][i],a[r][i]);//交换
		for(int i=n;i>=c;i--)	a[r][i]/=a[r][c];//同除
		for(int i=r+1;i<n;i++)
		{
			if(fabs(a[i][c])>eps)
			{
				for(int j=n;j>=c;j--)
					a[i][j]-=a[r][j]*a[i][c];//相加减
			}
		}
		r++;
	}
	
	for(int i=n-1;i>=0;i--)//回溯求解
	{
		for(int j=i+1;j<n;j++)
		{
			a[i][n]-=a[i][j]*a[j][n];
		}
	}
	
	if(r<n)
	{
		for(int i=r;i<n;i++)
		{
			if(fabs(a[i][n])>eps)
				return 2;//无解
			return 1;//有无穷多组解
		}
	}
	
	return 0;//有唯一解
}

int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n+1;j++)
		{
			cin>>a[i][j];
		}
	}
	int t=gauss();
	if(t==0)//有唯一解
	{
		for(int i=0;i<n;i++) printf("%.2lf
",a[i][n]);
	}
	else if(t==1)puts("Infinite group solutions");
	else puts("No solution");
	
	return 0; 
}

10.求组合数的方式

组合数定义:在(a)个元素中选取(b)个元素无序组合的所有方案数,记做(C_n^m)(C(n,m))

求组合数的公式:

[C_a^ b= frac{a imes (a-1) imes (a-2) imes cdots imes (a-b+1)}{1 imes 2 imes 3 imes cdots imes b} =frac{a!}{b!(a-b)!} ]

1.递推预处理

要求有模数 (m) 以防爆数据类型

由定义出发,我们很容易得到以下递推式:

[C_a^b=C_{a-1}^b+C_{a-1}^{b-1} ]

code:

#include <bits/stdc++.h>
using namespace std;
const int N=2010;
const int mod=1e9+7;

int c[N][N];

void init()
{
	for(int i=0;i<N;i++)
	{
		for(int j=0;j<N;j++)
		{
			if(!j) c[i][j]=1;
			else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
	}
}

2.预处理阶乘

要求模一个数 (m)

由定义(C_a^b==frac{a!}{b!(a-b)!})

我们很容易想到将阶乘以及阶乘模(m) 的逆元预处理出来

数组(fact[i]=i!,infact[i]=(i!)^{-1})

(C_a^b=fact[a] imes infact[a-b] mod m)

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=100010,mod=1e9+7;

ll fact[N],infact[N];//阶乘与阶乘的逆元

ll fpow(int a,int k,int p)
{
	ll res=1;
	while(k)
	{
		if(k&1) res=(ll)res*a%p;
		a=(ll)a*a%p;
		k>>=1;
	}
	return res;
}

int main()
{
	fact[0]=infact[0]=1;
	for(int i=1;i<N;i++)
	{
		fact[i]=(ll)fact[i-1]*i%mod;
		infact[i]=(ll)infact[i-1]*fpow(i,mod-2,mod)%mod; //快速幂求逆元
	}
	int n;
	scanf("%d",&n);
	while(n--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		ll ans=(ll)fact[a]*infact[b]%mod*infact[a-b]%mod;
		printf("%lld
",ans);
	}
	return 0;
}

3.(Lucas)定理

要求模一数(p)

内容:(C_a^b=C_{amod p}^{b mod p}cdot C_{a/p}^{b/p}(mod p))

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int p;

int fpow(int a,int k,int p)
{
	int res=1;
	while(k)
	{
		if(k&1) res=(ll) res*a%p;
		a=(ll) a*a%p;
		k>>=1;
	}
	return res;
}

int C(int a,int b)
{
	int res=1;
	for(int i=1,j=a;i<=b;i++,j--)
	{
		res=(ll) res*j%p;
		res=(ll) res*fpow(i,p-2,p)%p;
	}
	return res;
}

int lucas(ll a,ll b)
{
	if(a<p&&b<p) return C(a,b);
	return (ll)C(a%p,b%p)*lucas(a/p,b/p)%p;
}

int main()
{
	int n;
	cin>>n;
	while(n--)
	{
		ll a,b;
		cin>>a>>b>>p;
		cout<<lucas(a,b)<<endl;
	}
	return 0;
}

11.反素数

素数是除 (1) 以外因数个数最少的数,那么反素数就是因数最多的数。

严格的定义是这样的:

令函数 (g(x)) 表示正整数 (x) 的因数个数,显然 (g(x)in (1,+infty)) 。若对于任意 (0 < i <x ,iin Z) 都有(g(i)<g(x)) ,则称 (x) 为反素数。

(2 imes 10^9) 的数据范围下,反素数有以下性质:

  • (g(x)leq 9)

  • 每个质因子的次数最大为30

  • 若将质因子从左到右从小到大排序,则质因子的次数从左到右依次递减

(10^{18}) 的范围下,反素数的上述性质有些许变化:

  • (g(x)leq 16)

  • 每个质因子的次数最大为60

  • 第三条不变

根据性质,反素数数据量不大,所以求解反素数的方法是爆搜(((

P1463 [POI2002][HAOI2007]反素数

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int primes[110],tot=0;
bool mp[110];//筛100以内质数都够用到1e18范围了

int n;
int maxd,num_;//maxd: 约数个数,num_:反素数本身

void init()
{
	for(int i=2; i<=100; i++)
	{
		if(!mp[i]) primes[tot++]=i;
		for(int j=0; primes[j]*i<100; j++)
		{
			mp[primes[j]*i]=1;
			if(i%primes[j]==0) break;
		}
	}
}

void dfs(int x,int last,int s,int p)//枚举到哪个素数,剩多少次数,当前这个数大小,当前这个数的约数个数
{
	if(s>maxd||s==maxd&&p<num_)//约数个数更多或约数个数相等但当前数更小
	{
		maxd=s;
		num_=p;
	}
	if(x==9) return;//达到素数个数上限
	for(int i=1; i<=last; i++) //枚举次数
	{
		if((ll)p*primes[x]>n) break;//十年OI一场空,不开long long......
		p*=primes[x];
		dfs(x+1,i,s*(i+1),p);
	}
}

int main()
{
	init();
	scanf("%d",&n);
	dfs(0,30,1,1);
	printf("%d",num_);
	return 0;
}

(未完待续)

原文地址:https://www.cnblogs.com/IzayoiMiku/p/13569728.html