线性筛
线性筛可以求解积性函数等具有一些特殊性质的函数。核心思路在于,我们对于每个数 去枚举 ,其中 是小于 的质数。一般需要讨论三种情况:
- 是一个新发现的质数;
- ,说明 是 的质因子;
- 。
下面是几个例子。
欧拉函数
定义: 表示不超过 的正整数中,与 互质的个数。
性质:
- 对于互质的两个数是积性函数:若 则
- 如果 是质数,则有
在线性筛的基础上求出 ,其中 是质数:
-
如果 则 有 的所有因子,所以
-
否则,由于 和 互质,所以
想到这个分类讨论是来源于想用积性函数的性质,但是不一定互质,进一步想到不互质就一定是 。
vector<int> get_phi(int n) {
vector<bool> not_prime(n + 1);
vector<int> res;
vector<int> phi(n + 1);
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (not not_prime[i]) {
res.emplace_back(i);
phi[i] = i - 1;
}
for (auto&& x : res) {
if (i * x > n) break;
not_prime[i * x] = 1;
if (i % x == 0) {
// phi(n) = n * prod((p - 1) / p)
// => phi(i * x) = i * x * prod((p - 1) / p) = (i * prod((p - 1) / p)) * x = phi(i) * x,
// since `i` covers all factors of i * x
phi[i * x] = phi[i] * x;
break;
} else {
// i coprimes x
// phi(i * x) = phi(i) * phi(x) = phi(i) * (x - 1)
phi[i * x] = phi[i] * (x - 1);
}
}
}
return phi;
}
因数个数
设 表示 的最小质因子出现的次数, 表示 的因数个数。
- 当 为质数时,;
- 当 时,根据线性筛的性质可知 是 的最小质因子,所以我们更新 的值,并用它重新计算 ;
- 当 时,,因为此时 的最小质因子 不在 中出现。
pair<vector<int>, vector<int>> factcount(int n) {
vector<bool> not_prime(n + 1);
vector<int> res(n + 1), num(n + 1);
vector<int> primes;
res[1] = 1;
for (int i = 2; i <= n; ++i) {
if (not not_prime[i]) {
res[i] = 2;
num[i] = 1;
primes.emplace_back(i);
}
for (auto&& x : primes) {
if (i * x > n) break;
not_prime[i * x] = 1;
if (i % x == 0) {
num[i * x] = num[i] + 1;
res[i * x] = res[i] / num[i * x] * (num[i * x] + 1);
break;
}
num[i * x] = 1;
res[i * x] = res[i] * 2;
}
}
return {primes, res};
}
质因数个数
仍然讨论三种情况。设 表示 的质因子个数,则有
- 当 是质数时,;
- 当 时,显然 已经被 考虑过了,所以 ;
- 当 时,有 ,所以 。
TIP
这个问题也可以通过求出所有的质数、再枚举这些质数的倍数来求解,并且由于质数的个数是 所以复杂度同样是线性的。然而,实际经验表明这样做的常数会非常大,不如用线性筛直接求解。
vector<int> primecount(int n) {
vector<bool> not_prime(n + 1);
vector<int> primes;
vector<int> res(n + 1);
for (int i = 2; i <= n; ++i) {
if (not not_prime[i]) {
primes.emplace_back(i);
res[i] = 1;
}
for (auto&& x : primes) {
if (i * x > n) break;
not_prime[i * x] = 1;
if (i % x == 0) {
res[i * x] = res[i];
break;
}
res[i * x] = res[i] + 1;
}
}
return res;
}
最小质因数
vector<int> soe(int n) {
vector<bool> not_prime(n + 1);
vector<int> res;
vector<int> minp(n + 1);
minp[1] = 1;
for (int i = 2; i <= n; ++i) {
if (not not_prime[i]) {
res.emplace_back(i);
minp[i] = i;
}
for (auto&& x : res) {
if (ll(1) * i * x > n) break;
not_prime[i * x] = 1;
minp[i * x] = x;
if (i % x == 0) break;
}
}
return minp;
}
埃筛
埃筛虽然时间复杂度有 ,但是它可以只占用 is_prime
这个布尔数组的空间,用 bitset
还能进一步压缩。有的题目卡空间就是想让你用埃筛。
// @source https://oi-wiki.org/math/number-theory/sieve
vector<int> prime;
bool is_prime[N];
void Eratosthenes(int n) {
is_prime[0] = is_prime[1] = false;
for (int i = 2; i <= n; ++i) is_prime[i] = true;
for (int i = 2; i <= n; ++i) {
if (is_prime[i]) {
prime.push_back(i);
if ((long long)i * i > n) continue;
for (int j = i * i; j <= n; j += i)
is_prime[j] = false;
}
}
}