筛法
首先想到筛法,筛的肯定是素数
显然,埃氏筛,欧拉筛(线性筛)都是为了求出素数而产生的
再联想一个东西——积性函数
满足为\(f(ab) = f(a)f(b), [gcd(a, b) = 1]\)
完全积性函数则不需要满足\(a,b\)互质
那么我们在筛素数的时候就可以顺便把一些积性函数晒出来
先写基本筛法
埃氏筛
筛到一个素数,就把它所有的倍数全部标记成合数,没有标记过的就属于素数
效率接近线性,但由于\(24 = 2 \times 12 = 3 \times 8\),即一个数会被重复筛掉,所以非线性
时间复杂度是\(O(nloglogn)\)(显然我不会证明)
bool su[M];
void shai() {
su[1] = 1;
for (int i = 2; i <= n; i++) {
if (!su[i]) {//没被标记则素数
for (int j = i * i; j <= n; j += i) {//从素数的平方开始筛
su[j] = 1;
}
}
}
}
考虑如果想筛一些积性函数的话,可以用一个质因子对所有自己的倍数产生贡献的思想筛出积性函数
欧拉筛
既然埃氏筛的时间瓶颈在于一个数被多次标记,那么欧拉筛就保证了一个数只被自己最小的质因子筛掉
实现方法就是对于每个数\(i\)去枚举最小的质因子\(pr_j\),然后筛掉\(i \times pr_j\)
终止条件就是\(i \bmod pr_j = 0\)
意义就是\(i\)里面已经出现过了\(pr_j\),那我这个数可表示成\(pr_j^2 \times \frac{i}{pr_j}\),这样就不是被最小的质因子筛掉,所以直接break
就可以了
int pr[M], cnt;//存储已经筛出来的质数
bool su[M];
void shai() {
su[1] = 1;
for (int i = 2; i * i <= n; i++) {
if (!su[i]) pr[++cnt] = i;
for (int j = 1, k; j <= cnt and (k = i *pr[j]) <= n; j++)//需要保证i*pr[j]小于等于n
su[k] = 1;
if (!(i % pr[j])) break;
}
}
一般筛积性函数都是用欧拉筛来实现,常见的积性函数有欧拉函数,莫比乌斯函数,约数个数及约数和
int phi[M], miu[M], d[M], s[M];
void shai() {
su[1] = 1;
for (int i = 2; i <= n; i++) {
if (!su[i]) {
pr[++cnt] = i;
phi[i] = i - 1;
miu[i] = -1;
d[i] = 2;
s[i] = i + 1;
}
for (int j = 1, k; j <= cnt and (k = i *pr[j]) <= n; j++)//需要保证i*pr[j]小于等于n
su[k] = 1;
if (!(i % pr[j])) {
phi[k] = phi[i] * pr[j];
miu[k] = 0;
d[k] = 2 * d[i] - d[i / pr[j]];
s[k] = s[i] + (s[i] - s[i / pr[j]]) * pr[j];
break;
}
phi[k] = phi[i] * (pr[j] - 1);
miu[k] = -miu[i];
d[k] = 2 * d[i];
s[k] = s[i] * (pr[j] + 1);
}
}