1.29数论课笔记

Posted XHZS_XY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1.29数论课笔记相关的知识,希望对你有一定的参考价值。

o.O

一、\\(O(\\sqrtn)\\) 判断质数

枚举 \\(\\left[2,\\sqrtn\\right]\\) 中的数,判断是否能整除 \\(n\\) ,如果都没有则返回 \\(true\\)

为什么不用枚举 \\(\\sqrtn\\) 以上的数:
假设有一个数 \\(a\\in\\left[\\sqrtn,n\\right]\\)\\(n\\) 的约数,那么显然 \\(\\lfloor \\dfracna \\rfloor\\) 也是 \\(n\\) 的约数,而 \\(\\lfloor \\dfracna \\rfloor\\) 必然是小于 \\(\\sqrtn\\) 的,在之前就已经被检查过了。

点击查看代码
bool isprime(int x)
	for(int i=2;i*i<=x;i++)
		if(x%i)	return false;
	
	return true;

二、质因数分解

用一个 \\(vector\\) 存储质因数,遍历 \\(\\left[2,n\\right]\\) 中的所有数,检查是否是 \\(n\\) 的约数,如果是则反复除至 \\(n\\) 的因子不含有当前枚举的数。

为什么遍历的是所有的数,筛出来的却都是质数因数:
假设 \\(n\\) 有一个合数约数 \\(x\\),那么 \\(x\\) 本身还可以写成若干个小于 \\(x\\) 的质数相乘,而这些小于 \\(x\\) 的数已经在之前筛过了。

点击查看代码
vector<int> p;
void work(int x)
	for(int i=2;i<=x;i++)
		while(x%i==0)
			p.emplace_back(i);
			x/=i;
		
	

三、算术基本定理

对于所有的大于1的整数 \\(a\\) 都有:

\\[a=\\prod_i=1^kp_i^c_i,p\\in Prime \\]

事实上就是分解质因数,把 \\(a\\) 分解成 \\(k\\) 个质数的若干次幂之积。
代码基本同分解质因数。

点击查看代码
vector<int> p,c;
void work(int x)
	for(int i=2;i<=x;i++)
		int cnt=0;
		while(x%i==0)
			if(!cnt)	p.emplace_back(i);
			x/=i,cnt++;
		
		if(cnt)	c.emplace_back(cnt);
	

//k=p.size()或c.size()

四、埃氏筛质数

筛除每个质数的倍数,剩下的即为质数。

五、欧拉筛质数

欧拉筛相对埃氏筛的进步就是最大限度地减少了重复筛出合数的情况。

1.外层枚举一个数 \\(i\\)

for(int i=2;i<=n;i++)

2.如果在这之前 \\(i\\) 没有被标记为合数(即 \\(c_i=1\\)),就将 \\(i\\) 加入质数集合

if(!c[i]) p.emplace_back(i);

3.然后枚举所有的已知质数,作为一个“基底”\\(base\\)

for(int base:p)

4.先来检查 \\(base\\)\\(i\\) 倍是否已经超出了 \\(n\\),如果是直接跳出内层枚举

if(i*base>n) break;

5.将 \\(base\\)\\(i\\) 倍标记为合数

c[base*i]=1;

6.如果 \\(i\\)\\(base\\) 的倍数,跳出内层枚举防止重复筛除

if(i%base==0) break;

\\(i\\)\\(base\\) 的倍数时就跳出内层,为什么能防止重复:
还是记内层枚举到的质数为 \\(base\\)
首先要理解到 \\(i\\) 的含义在枚举内层时发生了转变。
——在外层时,\\(i\\) 的含义就是要被检查是否为质数的一个数
——在内层时,\\(i\\) 成为了“倍数”,表示现在要标记 \\(base\\)\\(i\\) 倍为合数
注意到并不是筛除 \\(i\\)\\(base\\) 倍,而是筛除 \\(base\\)\\(i\\) 倍!
然后来讨论为什么 \\(i\\)\\(base\\) 的倍数后面就会重复。
我们设 \\(i=a*base\\),其中 \\(a\\) 是不为 \\(1\\) 的整数。
假设我们当前已经满足i%base==0,但不跳出,就会枚举到下一个质数,记其为 \\(base\'\\)
这样我们就会尝试标记 \\(base\'\\)\\(i\\) 倍为合数。
虽然它确实是合数,但它也能被 \\(base\\) 的其他倍筛去,这就导致了重复。
具体地,我们设当前试图筛去的数为 \\(k\\),即 \\(k=base\'*i\\)
由于之前有 \\(i=a*base\\),所以 \\(k\\) 也能写成 \\(k=base*(a*base\')\\)
这也就意味着,目前的 \\(k\\) 也会在外层 \\(i\\) 枚举到 \\(a*base\'\\)、内层再次枚举到 \\(base\\) 时作为“\\(base\\)\\(a*base\'\\) 倍”被筛去
举个例子,如果 \\(i=4,base=2\\),按理说就应该跳出,若不跳出枚举到下一个 \\(base\'=3\\),试图筛去的 \\(k=base\'*i=3*4=12=2*6=base*6\\)

如何保证在 \\(i\\) 枚举到 \\(a*base\'\\) 时,内层循环不会在再次枚举到 \\(base\\) 之前跳出:
注意到 \\(i=a*base\\) 中,\\(a\\) 的最小因子一定是不小于 \\(base\\) 的。
如果 \\(a\\) 有小于 \\(base\\) 的因子,那么内层循环一定会在枚举到 \\(base\\) 之前就跳出
又因为 \\(base\'\\) 显然大于 \\(base\\)
所以 \\(a*base\'\\) 的最小因子也是大于 \\(base\\) 的,这就意味着当 \\(i\\) 枚举到 \\(a*base\'\\)时,其内层循环在枚举到 \\(base\\) 之前不会存在满足i%base==0这个语句的情况,因为那当且仅当 \\(a*base\'\\) 有小于 \\(base\\) 的因数时才有可能发生。

欧拉筛整体代码

点击查看代码
bool c[MAXN];
vector<int> p;
void work(int n)
	for(int i=2;i<=n;i++)
		if(!c[i])	p.emplace_back(i);
		for(int base:p)
			if(i*base>n)	break;
			c[base*i]=1;
			if(i%base==0)	break;
		
	

七、gcd,lcm,exgcd

gcd与exgcd的具体证明在之前的文章
这里给出 \\(\\gcd(a,b)*\\operatornamelcm(a,b)=a*b\\) 的证明。

先考虑证明互质的两数 \\(p,q\\) 的最小公倍数为 \\(p*q\\)
由于显然 \\(p*q\\)\\(p,q\\) 的公倍数,设其不是最小公倍数
则存在一个整数 \\(k\\),使得 \\(\\dfracp*qk\\) 仍为整数且是 \\(p\\)\\(q\\) 的倍数
因为是 \\(p\\) 的倍数,则 \\(\\dfrac\\dfracp*qkp=\\dfracp*qp*k=\\dfracqk\\) 为整数
因为是 \\(q\\) 的倍数,则 \\(\\dfrac\\dfracp*qkq=\\dfracp*qq*k=\\dfracpk\\) 为整数
这意味着 \\(\\gcd(p,q)=k\\) 因为 \\(p=\\dfracpk*k,q=\\dfracqk*k\\)\\(\\dfracpk,\\dfracqk\\) 均为整数,与 \\(p,q\\) 互质矛盾,故互质的两数 \\(p,q\\) 的最小公倍数为 \\(p*q\\)

再证明 \\(\\gcd(a,b)*\\operatornamelcm(a,b)=a*b\\)
\\(\\gcd(a,b)=g\\),则\\(a=a\'g,b=b\'g\\)
显然此处 \\(\\gcd(a\',b\')=1\\),否则 \\(\\gcd(a,b)\\) 应为 \\(g*\\gcd(a\'*b\')\\)
于是我们知道 \\(\\operatornamelcm(a\',b\')=a\'*b\'\\)
又注意到 \\(\\operatornamelcm\\) 满足结合律,即 \\(\\operatornamelcm(ac,bc)=\\operatornamelcm(a,b)*c\\),证明显然
又因为 \\(\\operatornamelcm(a,b)=\\operatornamelcm(a,b)\\)
所以 \\(\\operatornamelcm(a,b)=\\operatornamelcm(a\'g,b\'g)=\\operatornamelcm(a\',b\')*g=a\'*b\'*g\\)
两边同乘 \\(g\\)\\(g*\\operatornamelcm(a,b)=a\'*g*b\'*g\\)
由一开始的三个定义 \\(\\gcd(a,b)=g,a=a\'g,b=b\'g\\) 替换得
\\(\\gcd(a,b)*\\operatornamelcm(a,b)=a*b\\)

给出三个算法的代码

点击查看代码
int gcd(int a,int b)
	return b==0?a:gcd(b,a%b);

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

void exgcd(int a,int b,int &x,int &y)
	if(b==0)
		x=1,y=0;
	else
		exgcd(b,a%b,y,x);
		y-=(a/b)*x;
	

八、gcd的减半性

如果有一个序列:

\\[a_1,a_2,a_3,a_4,……,a_n \\]

让你求它的前缀gcd,即求:

\\[\\operatornameg(i)=\\gcd(a_1,a_2,a_3,a_4,……,a_i) \\]

则有 \\(g(i)=g(i-1)\\)\\(\\operatornameg(i)\\leq\\dfrac\\operatornameg(i-1)2\\)

如欲证明只需分类讨论:
\\(\\operatornameg(i)=\\operatornameg(i-1)\\)
意味着 \\(a_i\\)\\(\\operatornameg(i-1)\\) 的倍数,此情况显然
而若 \\(a_i\\) 不是 \\(\\operatornameg(i-1)\\),则明显有 \\(\\operatornameg(i)<\\operatornameg(i-1)\\)
\\(\\operatornameg(i)\\) 一旦变小,代表着 \\(\\operatornameg(i-1)\\) 有一个 \\(a_i\\) 没有的因子,而这个因子至少为 \\(2\\),因而 \\(\\operatornameg(i)\\) 一旦变小,至少缩小至 \\(\\operatornameg(i-1)\\)\\(\\dfrac12\\)(即至少从 \\(\\operatornameg(i-1)\\) 中除去了一个因子 \\(2\\)

九、与约数相关的数论函数

以下均在算术基本定理的基础上。

\\[a=\\prod_i=1^kp_i^c_i,p\\in Prime \\]

1.约数个数函数

表示一个数 \\(n\\) 有多少个约数

\\[\\operatornamed(n)=\\prod_i=1^k(c_i+1) \\]

证明:
一个数的约数肯定是由它的若干个质因数乘出来的,否则它不可能是约数,因为不能整除
考虑一个质因数 \\(p_i^c_i\\),表示有 \\(n\\)\\(c_i\\) 个质因子 \\(p_i\\)。考虑约数里会有几个 \\(p_i\\),显然共有 \\(c_i+1\\) 种情况,最后由乘法原理得 \\(\\operatornamed(n)=\\prod_i=1^k(c_i+1)\\)

2.约数和函数

表示一个数 \\(n\\) 所有约数的和

\\[\\sigma(n)=\\prod_i=1^k\\sum_j=0^c_ip_i^j \\]

证明:
约数和函数依照定义的写法就是

\\[\\sigma(n)=\\sum_k|nk=k_1+k_2+……+k_\\operatornamed(n) \\]

考虑对这个式子做因数分解即可。
也可以同时拆开公式,即写成下面的形式:

\\[\\sigma(n)=(p_1^0+p_1^1+……+p_1^c_1)*……*(p_\\operatornamed(n)^0+p_\\operatornamed(n)^1+……+p_\\operatornamed(n)^c_1) \\]

拆开括号会发现每一项都对应着 \\(n\\) 的一个约数,例如会有一项可能是:

\\[\\sigma(n)=……+(p_1^1 p_2^3p_3^0……p_\\operatornamed(n)^5)+…… \\]

这显然就是一个 \\(n\\) 的约数,所有的加起来也就是约数和了。

3.欧拉函数

表示在小于 \\(n\\) 的数中与 \\(n\\) 互质的数的数量。

\\[\\phi(n)=\\prod_i=1^kp_i^c_i-1(p_i-1) \\]

证明:
先考虑如何证明一个质数 \\(p\\)\\(x\\) 次幂,\\(\\phi(p^x)=p^x-p^x-1\\)
这个很显然,把所有的数 \\(p^x\\) 减去 \\(p\\) 的倍数个数 \\(p^x-1\\) 剩下的就是与 \\(p^x\\) 互质的数个数
再考虑证明欧拉函数是一个积性函数(对于 \\(\\gcd(a,b)=1\\)\\(\\phi(ab)=\\phi(a)\\phi(b)\\)
设这两个互质的数为 \\(a\\)\\(b\\)
则显然可以有 \\(a=p^x,b=q^y\\) 其中 \\(p,q\\in Prime\\)
\\(ab=p^xq^y\\)
\\(\\operatornamem(n,k)\\) 表示小于 \\(n\\) 的数中 \\(p\\) 的倍数的数量

\\[\\operatornamem(ab,p)=\\dfracabp=\\dfracp^xq^yp=p^x-1q^y \\\\ \\operatornamem(ab,q)=\\dfracabq=\\dfracp^xq^yq=p^xq^y-1 \\\\ \\operatornamem(ab,pq)=\\dfracabpq=\\dfracp^xq^yq=p^x-1q^y-1 \\]

\\(\\leq ab\\) 的数无非只有四种:

  • \\(p\\) 的倍数,不是 \\(q\\) 的倍数
  • \\(q\\) 的倍数,不是 \\(p\\) 的倍数
  • \\(pq\\) 的公倍数
  • 既不是 \\(p\\) 的倍数,也不是 \\(q\\) 的倍数
    注意到最后一种其实就是 \\(\\phi(ab)\\),由容斥原理可得

\\[ab=\\operatornamem(ab,p)+\\operatornamem(ab,q)-\\operatornamem(ab,pq)+\\phi(ab) \\]

移项可得

\\[\\phi(ab)=ab-\\operatornamem(ab,p)-\\operatornamem(ab,q)+\\operatornamem(ab,pq) \\\\ \\phi(ab)=p^xq^y-p^x-1q^y-p^xq^y-1+p^x-1q^y-1 \\]

因式分解可得

\\[\\phi(ab)=(p^x-p^x-1)(q^y-q^y-1)=\\phi(p)\\phi(q) \\]

最后考虑证明公式,由算数基本定理可得:

\\[\\phi(n)=\\phi(\\prod_i=1^kp_i^c_i) \\\\=\\prod_i=1^k\\phi(p_i^c_i) \\\\=\\prod_i=1^k(p_i^c_i-p_i^c_i-1) \\\\=\\prod_i=1^kp_i^c_i-1(p_i-1) \\]

以上是关于1.29数论课笔记的主要内容,如果未能解决你的问题,请参考以下文章

2021-2022-2 ACM集训队每周程序设计竞赛 - 问题 D: 上数论课 - 题解

2017.8.9数论课小结

基础数论笔记

算法笔记--数论模板小集(待增)

数论模板

数论-欧拉函数 学习笔记