数论学习笔记
取模的性质
性质一:\(a\%b=a-\lfloor\frac ab\rfloor\cdot b\)
证明:
设 \(a\div b=c……d\),则 \(a=bc+d\)。
在 c++
中,有 \(a/b=c,a\%b=d\)。将 \(c,d\) 代入,得:
移项,得 \(a\%b=a-\lfloor\frac ab\rfloor\cdot b\)。
证毕。
性质二:\((a+b)\%p=(a\%p+b\%p)\%p\)
证明:
设 \(a\div p=a_1……a_2,b\div p=b_1……b_2\),则 \(a=pa_1+a_2,b=pb_1+b_2\)。
于是有:
因此,\((a+b)\%p=(pa_1+a_2+pb_1+b_2)\%p=(a_2+b_2)\%p=(a\%p+b\%p)\%p\)。「乘 \(p\) 的直接消掉」
证毕。
性质三:\((a-b)\%p=(a\%p-b\%p)\%p\)
证明:
还是用性质二的证明中设出来的 \(a_1,a_2,b_1,b_2\)。
\((a-b)\%p=(pa_1+a_2-pb_1-b_2)\%p=(a_2-b_2)\%p=(a\%p-b\%p)\%p\)
证毕。
性质四:\((a\times b)\%p=(a\%p\times b\%p)\%p\)
证明:
还是用性质二的证明中设出来的 \(a_1,a_2,b_1,b_2\)。
证毕。
性质五:c++
的取模运算中,被除数与余数符号相同。
即:若 \(a\%b=c\),则 \(a、c\) 同号,\(c\) 的符号与 \(b\) 无关。
证明:语法特性,无需证明。
gcd
和 lcm
定理 & 推导
「首先有定义:\(\gcd(a,b),\text{lcm}(a,b)\) 分别表示正整数 \(a,b\) 的最大公约数和最小公倍数,特殊地,规定 \(\gcd(0,a)=a\)」
定理一:若 \(a>b\),则 \(\gcd(a,b)=\gcd(a-b,b)\)
证明:
设 \(\gcd(a,b)=x\),则 \(\exist a',b'\in \N^+\),使得 \(a=a'x,b=b'x\) 且 \(\gcd(a',b')=1\)。
将其带入,得:\(\gcd(a-b,b)=\gcd(a'x-b'x,b'x)=x\gcd(a'-b',b')\)。
现在只需证明 \(x=x\gcd(a'-b',b')\),即 \(\gcd(a'-b',b')=1\) 即可。
不妨设 \(\gcd(a'-b',b')=y\),则 \(\exist c,d\in \N^+\),使得 \(a'-b'=cy,b'=dy\) 且 \(\gcd(c,d)=1\)。
将二式相加,得:\(a'=a'-b'+b'=cy+dy\),故 \(\gcd(a',b')=\gcd(cy+dy,dy)=y\gcd(c+d,d)\)。
由于 \(\gcd(a',b')=1\),可以得到:\(y\gcd(c+d,d)=1\)。
因为都是正整数,所以 \(y=\gcd(c+d,d)=1\),故 \(\gcd(a'-b',b')=1\),故 \(\gcd(a,b)=\gcd(a-b,b)\)。
证毕。
定理二:若 \(a>b\),则 \(\gcd(a,b)=\gcd(a\%b,b)\)「辗转相除法」
证明:
由定理一很容易得到:
其中 \(a-kb<b\)。
易得 \(k=\lfloor\frac ab\rfloor\),由于 \(a-\lfloor\frac ab\rfloor\cdot b=a\%b\),故 \(a-kb=a\%b\)。
证毕。
定理三:\(\text{lcm}(a,b)=\frac{ab}{\gcd(a,b)}\)
证明:
设 \(\gcd(a,b)=x\),则 \(\exist a',b'\in \N^+\),使得 \(a=a'x,b=b'x\) 且 \(\gcd(a',b')=1\)。
于是 \(ab=a'x\cdot b'x,\text{lcm}(a',b')=a'b'\)。
证毕。
gcd
代码「递归式」
由定理二可以写出递归代码:
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
gcd
代码「迭代式」
发现每次调用 \(\gcd\) 函数时,要保证第二个参数小于等于第一个参数,因此只需每次迭代将当前参数互换「直接用异或可以避免引入第三个参数」,即可避免递归。
迭代代码:
inline int gcd(int a,int b)
{
while(b) a%=b,a^=b^=a^=b;
return a;
}
gcd
代码时间复杂度简单分析
一次递归/迭代中,\(\gcd(a,b)\) 可以转化为 \(\gcd(b,a\%b)\),可以看作其中 \(b\) 不变,\(a\) 变为 \(a\%b\)。
那么如何具体算出 \(a\) 减少了多少呢?
我们都知道,\(a\div b=c……d\) 可以写为 \(a=b\times c+d\) 的形式。
不妨设 \(a\div b=c……d\),则 \(a\%b=d\)。
要具体分析 \(d\) 的大小,可以对 \(b\) 的大小进行分类讨论:
- 若 \(b>\frac a2\),显然 \(c=0,d=a-b<\frac a2\)
- 若 \(b\le\frac a2\),显然 \(d<b\le\frac a2\)
经过一次迭代,\(a\) 的规模减小了至少一半,而经过两次迭代,\(b\) 的规模也减少了至少一半。
因此,无论何种情况,经过两次迭代,要求解的规模一定小于等于原来的一半,最坏运行次数不会超过 \(2\log_2\min\{a,b\}。\)
因此,可以不太严谨地说,辗转相除法求解 gcd
的时间复杂度近似为 \(\log\min\{a,b\}\)。「反正严谨的证明我也不会略略略」
lcm
代码
根据定理三,闭着眼都可以写出代码:
inline int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
莫比乌斯反演
数论函数:定义域为正整数的函数称为数论函数。
积性函数:如果 \(\forall a、b,(a,b)=1,f(ab)=f(a)f(b)\),这样的数论函数称为积性函数。
常见的数论函数:
-
欧拉函数「如果 \((a,b)=1\),则有 \(\varphi(ab)=\varphi(a)\varphi(b)\)」
-
莫比乌斯函数
-
除数函数 「用 \(d_k(n)\) 表示,其值等于所有 \(n\) 的因子的 \(k\) 次方之和」
\(e.g.\)
\(d_2(12)=1^2+2^2+3^2+4^2+6^2+12^2=219+d_1(5)=1^1+5^1=6,d_1(7)=1^1+7^1=8\)
\(\because(5,7)=1\)
\(\therefore d_1(35)=d_1(5*7)=d_1(5)*d_1(7)=6*8=48\)
验证一下:
\(d_1(35)=1^1+5^1+7^1+35^1=48\)
(和欧拉函数有些类似)
-
-
完全积性函数:如果 \(\forall a、b,f(ab)=f(a)f(b)\),这样的数论函数称为完全积性函数。
- 常见的完全积性函数:\(f(x)=x\)