约数相关
约数
约数简介
定义:
若整数 n 除以整数 d 的余数为 0,即 d 能整除 n, 则称 d 是 n,的约数,n 是 d 的倍数,记为 d|n
在算术基本定理中 \(N\)可被分解成下面这个样子
那么\(N\)的正约数个数为:
\(N\)的所有正约数和为:
求解\(N\)的正约数集合
- 试除法
$ \qquad $ 如果一个数\(x\)是\(N\)的约数,那么\(N/d≤\sqrt N\)也为\(N\)的约数。
$ \qquad $ 因为约数总是成对出现,因此扫描 \(x=1-\sqrt N \ ∈Z\),尝试是否 \(x|N\)。但是我们要特判完全平方数,因为对于完全平方数\(\sqrt N \ ∈Z\)。
int factor[1600], num = 0;
for(int i = 1; i * i <= n; i++) {
if(n % i == 0) {
factor[++num] = i;
if(i != n/i)
factor[++num] = n / i;
}
}
$ \qquad $ 求\(1-\sqrt N\)每个数的正约数集合——倍数法
基本思想:
不同于试除法,我们可以反过来考虑每个数\(x\),则以\(x\)为约数的数就是\(x,2x,3x…\)
vector<int> factor[SIZE];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n / i; j++)
factor[i*j].push_back(i);
//输出
for(int i = 1; i <= n ;i++) {
for(int j = 0; j < factor[i].size; j++)
printf("%d ",factor[i][j]);
putchar('\n');
}
$ \qquad $ 在小数据内(\(N∈[4,16]\)),试除法是要快于倍数法的,复杂度为\(O(N\sqrt N)\)
约数相关内容:
1.最大公约数|\(gcd\)
- 定义:
若自然数\(x\)满足 \(x|a\)和\(x|b\),则称\(x\)是\(a\)是\(b\)的公约数,则\(max(x)\)就是\(a\)和\(b\)的最大公约数,记为\(gcd(a,b)\)。
同理,若同时满足\(a|x\)和\(b|x\),则\(x\)是\(a\)和\(b\)的公倍数,在这样的\(x\)中最小的一个,为\(a,b\)的最小公倍数,记为\(lcm(a,b)\)。
定理:
以上定义的证明读者可以自行尝试,也可以《参考算法竞赛进阶指南》
- 求解方法
在浅谈质因数分解中我们已经提到了求解\(gcd\)的算法:1.更相减损术 2.辗转相除法。
《九章算术》有:
以上两条定理很重要,这涉及我们后面的二进制优化\(gcd\)
欧几里得算法:
这样就得出了我们熟悉的辗转相除法:
//递归形式
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
//非递归形式
int gcd(int a, int b) {
int temp;
while(b) {
temp=a % b;
a = b;
b = temp;
}
return a;
}
$ \qquad \qquad \(欧几里得算法时间复杂度为\)O(log(a+b))$。对于高精度算法,取模不容易实现,建议使用更相减损术代替。
$ \qquad \qquad \(当然,\)gcd$还可以优化(就是前面提到的二进制优化)。
- 为什么可以优化?因为辗转相除法运用了模运算,这样常数大运行慢,故用更相减损+二进制优化\(gcd\)
-
\(a=0, \quad return \quad b; \quad \mid \quad b=0, \quad return \quad a;\)
-
\(a<b,a \quad xor= \quad b, \quad b \quad xor= \quad a, \quad a \quad xor= \quad b;\)(独特的二进制交换)
-
\(a\) & \(1\) 且 \(b\) & \(1\),$ \quad ans=2gcd(a >> 1,b>>1);$
-
\(a\) & \(1\) 且 !\(b\) & \(1\),$ \quad ans=gcd(a >> 1,b);$
-
!\(a\) & \(1\) 且 \(b\) & \(1\),$ \quad ans=gcd(a,b >> 1);$
-
!\(a\) & \(1\) 且 !\(b\) & \(1\),$ \quad ans=gcd(a - b,b)$。
//下面代码返回gcd(a,b)的值同时把b赋予这个值,不需要可以把&去掉
int gcd(int a,int &b) {
if(a == 0 || b == 0) return b = a + b;
int n = 0, m = 0;
for(; !(a & 1); a >>= 1, n++);
for(; !(b & 1); b >>= 1, m++);
n = m < n ? m : n;
while(a) {
if(a < b) { a ^= b, b ^= a, a ^= b;}
if(!(a -= b)) return b <<= n;
while(!(a & 1)) a >>= 1;
}
}
但注意上面代码在负数的时候是错误的,因为负数的右移\(1\)位和除\(2\)出不一样的,所以遇到负数时上面的位运算要用正常除法代替。
2.互质与欧拉函数
- 定义:
\(∀a,b∈N\),若\(gcd(a,b)=1\),则称\(a,b\)互质。
对于三个数及以上的情况类比即可,这里不再赘述,读者可以自行查阅相关资料。
欧拉函数
$ \qquad $ \(1-N\)中与\(N\)互质的个数被称为欧拉函数,记为\(φ(N)\)。
在算数基本定理中,\(N=\prod_{i=1}^m p_i^ {c_i}\),则:
根据欧拉函数的计算式,我们只需要分解质因数,即可求出欧拉函数:
//参考代码(源于《算法竞赛进阶指南》)
int phi(int n) {
int ans = n;
for(int i = 2; i <= sqrt(n); i++)
if(n % i == 0) {
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
if(n > 1) ans = ans / n * (n - 1);
return ans;
}
- 欧拉函数的性质:
-
\(∀n>1,1-n\)中与\(n\)互质的数和为\(n*φ(n)/2\)。
-
若\(gcd(a,b)=1\),即\(a,b\)互质,则\(φ(ab)=φ(a)φ(b)\)。
-
设\(p\)为质数,若 \(p \mid n\) 且 \(p^2 \mid n\),则\(φ(n)=φ(n/p)*p\)。
-
设\(p\)为质数,若 \(p \mid n\) 且 \(p^2 \nmid n\),则\(φ(n)=φ(n/p)*(p-1)\)。
-
\(\sum_{d \mid n}φ(d)=n\)。
积性函数:
$ \qquad $ 如果\(gcd(a,b)=1\),有\(f(ab)=f(a)f(b)\),那么称函数\(f\)为积性函数
- 若\(f\)是积性函数,且在算术基本定理中\(n=\prod_{i=1}^m p_i^ {c_i}\),则$ \ f(n)=\prod_{i=1}^m f(p_i^ {c_i})$。
(第六点同为欧拉函数的性质)
\(*\)完全积性函数:
关于积性函数的拓展非常多,内容也比较深奥,下面简单介绍下:
常用积性函数如下
-
\(φ(n)\) —— 欧拉函数
-
\(σ(n)\) —— 约数和函数
-
\(μ(n)\) —— 莫比乌斯函数
-
\(σ_0(n)\) —— 约数个数函数
-
\(σ_k(n)\) —— 约数次数和函数
-
\(gcd(n,k)\) —— 最大公约数函数,当\(k\)固定时
-
\(1(n)=1\) ——这个我也不知道是什么
-
\(f(n)=n\)——我还是不知道是什么
$ \qquad $还有一点,就是积性函数都是可以线性筛的
- 狄利克雷卷积
首先先补充下数论函数的定义:
-
陪域:包含值域的任意集合
-
数论函数:定义域为正整数,陪域为复数的函数
好了,我们可以开始介绍狄利克雷卷积了。
定义\(f,g\)为数论函数,则它们的狄利克雷卷积可以表示为\(f*g\),设\(h=f*g\):
若\(f,g\)是积性函数,显然,\(h\)也是积性函数。
证明
$ \qquad $ 设\(n = a*b\)且\(a,b\)互质,即\(gcd(a,b)=1\):
$ \qquad $证毕。
运算法则
狄利克雷卷积的运算满足:
-
\(f*g=g*f\)(交换律)
-
\((f*g)*h=f*(g*f)\)(结合律)
-
\(f*(g+h)=f*g+f*h\)(分配律)
-
若\(f,g\)是积性函数,则\(f*g\)也是积性函数。(性质)
狄利克雷卷积相关
下面还是回到欧拉函数:
$ \qquad $ 我们可以利用 \(Eratosthenes\) 筛法,按照欧拉函数的计算式,在\(O(NlogN)\)时间内求解出 \(2-N\)中每个数的欧拉函数。
int phi[SIZE];
void eluer(int n) {
for(int i = 2; i <= n; i++) phi[i] = i;
for(int i = 2; i <= n; i++)
if(phi[i] == i)
for(int j = i; j <= n; j += i;)
phi[j] = phi[j] / i * (i - 1);
}
$ \qquad $ 但是既然说了积性函数都是可以线性筛的,那么欧拉函数如何优化成线性的呢?让我们来回顾质数线性筛的思想(质数筛法详解),线性筛中,每个合数\(n\)只会被它的质因子筛一次,利用下面几条性质:
-
定理三:设 \(p\) 为质数,若 $p \mid n \ \(且\) \ p^2 \mid n\(,则\)φ(n)=φ(n/p)*p$。
-
定理四:设 \(p\) 为质数,若 $p \mid n \ \(且\) \ p^2 \nmid n\(,则\)φ(n)=φ(n/p)*(p-1)$
我们就可以在筛选合数时运用这两条定理,从\(φ(n/p)\)递推到\(φ(n)\)。
关于质数筛法,因为有两种线性筛的写法,其实是大同小异的,但是为了读者方便,这里都给出:
//法一 :
int v[SIZE], pri[SIZE], phi[SIZE], num;
void promoted_eluer(int n) {
for(int i = 2; i <= n; i++) {
if(v[i] == 0)
pri[++num] = i, phi[i] = i - 1;
for(int j = 1; j <= num && i * pri[j] <=n; j++) {
v[i * pri[j]] = 1;
phi[i * pri[j]]=
phi[i] * (i % pri[j] ? pri[j] - 1 : pri[j]);
if(i % pri[j] == 0) break;
}
}
}
//法二:
int v[SIZE], pri[SIZE], phi[SIZE], num;
void promoted_eluer(int n) {
for(int i = 2; i <= n; i++) {
if(v[i] == 0)
pri[++num] = i, phi[i] = i - 1, v[i] = i;
for(int j = 1; j <= num; j++) {
if(pri[j] > v[i] || pri[j] > n / i) break;
v[i * pri[j]] = pri[j];
phi[i * pri[j]]=
phi[i] * (i % pri[j] ? pri[j] - 1 : pri[j]);
}
}
}
※章末练习
\(END\)
\(PS:\)
以上讲解顺序及内容参考:李煜东《算法竞赛进阶指南》