筛法

Sieve of Eratosthenes

https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
O(n/2+n/3+n/5+n/7+..)=O(nloglogn)O(n/2 + n/3 + n/5 + n/7 + .. ) = O(n \log \log n)

O(nloglogn)O(n \log \log n) 枚举每个质数,筛到质数的倍数

for (int i = 2; i <= n; i++)
{
    if (v[i] == 0)
    {
        for (int j = i; j <= n; j += i)
        {
            // if (v[j] == 0) 有的人只有在v[j]是0还没有被筛的情况下,做下面的事情
            v[j] = i; // j 的质因数是 i
            // 如果不加 if 同一个数字会被筛多次,v[j] 记录的是 j 的最大的质因数
            // 如果加 if 同一个数字也会被筛多次(如果想值筛一次,用欧拉筛法) v[j] 是 j 的最小的质因数
        }
    }
}
// 如果 v[i] == i 意味着 i 是质数

这个写法一个数组记录最小的质因数(或者说是最大的质因数)
如果需要压缩空间,也可以用 01数组 (bitset) 记录是否是质数

Euler's sieve

时间复杂度 O(n)O(n) 可以保证每个数只被筛一次
和上一个方法相比,需要一个数组记录到目前为止生成了哪些质数

void init() {
    // vis[i] 表示 i 最小的质因数
    for (int i = 2; i < MAXN; ++i) {
        if (!vis[i]) { // i 没有被筛掉,i是质数
            pri[cnt++] = i; // 把i加入质数数组
            vis[i] = i;
        }
        for (int j = 0; j < cnt && i * pri[j] <= n; ++j) {
            // 无论i是不是质数,i都和已有的所有质数尝试一下,把尝试的结果标记成合数
            vis[i * pri[j]] = pri[j];
            // 标记 i * pri[j] 的质因数是 pri[j]
            // 同一个位置可能被标记多次(12 = 4 * 3 = 6 * 2)
            // 目标让每个数字只被标记一次
            // 要求 pri[j] 必须是 i*pri[j] 的最小质因数
            if (vis[i] == pri[j]) // 也可以写作 i % pri[j] == 0 之这么写是为了避免除法,加速运算
            {
                break;
                // 如果 i 包含 质因数 pri[j]
                // 之后比如对于 i*pri[j+1] 这个数字 pri[j+1] 不是最小质因数
            }
        }
    }
}

最小质因数

可以用筛法求出 1到n中的质数 和 每个数字的最小质因数
如果知道 每个数字的最小质因数,那么可以很快的分解质因数

筛法预处理积性函数的值

phi[1] = 1 // 欧拉函数
mu[1] = 1 // 莫比乌斯函数
dcnt[1] = 1 // 约数个数
dsum[1] = 1 // 约数和
for (int i = 2; i <= n; i++)
{
    if (v[i] == v[i / v[i]]) // 如果 i 和 i/v[i] 最小质因数相等,意味着 i 中 v[i] 的次数大于1
    {
        phi[i] = phi[i / v[i]] * v[i];
        mu[i] = 0;
        dcnt[i] = 2 * dcnt[i / v[i]] - dcnt[i / v[i] / v[i]];
        dsum[i] = dsum[i / v[i]] * v[i] + (dsum[i / v[i]] - dsum[i / v[i] / v[i]] * v[i]);
    }
    else
    {
        phi[i] = phi[i / v[i]] * (v[i] - 1);
        mu[i] = -mu[i / v[i]];
        dcnt[i] = dcnt[i / v[i]] * 2;
        dsum[i] = dsum[i / v[i]] * (1 + v[i]);
    }
}

P3383 【模板】线性筛素数

https://www.luogu.com.cn/problem/P3383

P1835 素数密度

有一个做法可以直接求 1 到 n 个质数个数
注意到 R-L <= 1e6
注意到 2 ** 31 以下的合数,一定有一个 <=sqrt(R) 的质因数
用一个长度为 R-L+1 的数组存每个数字是否是合数
f[i] 表示 i 是否是合数
如果 f[i] == 1 表示 L+i 是合数
如果 f[i] == 0 表示 L+i 是质数

枚举所有 <= sqrt(R) 的质数 p
枚举在L到R之间的,所有 p 的倍数(不包括 p 本身)设为i
标记i是合数

#include <bits/stdc++.h>
using namespace std;
long long l, r;
int v[1000020];
// v[i] 表示 i 的最大质因数是多少
// 最后一次被谁筛掉,比如 30 会被 2 3 5 筛掉 3 次,最后 v[30]=5
int f[1000020];
// f[i] 表示 l+i 是不是合数,如果 f[i]==1 说明 l+i 被某个质数筛掉过,是合数
void gao(int p)
{
    // 枚举 p 的所有倍数
    // 但是不枚举1倍,p本身是质数!至少2倍
    for (long long i = max((l + p - 1) / p, 2LL) * p; i <= r; i += p)
    {
        f[i - l] = 1;
    }
}
int main()
{
    scanf("%lld%lld", &l, &r);
    // 计算出 <= sqrt(r) 所有质数
    for (int i = 2; i <= r / i; i++)
    {
        // 如果i没有被标记过,说明i是质数
        if (!v[i])
        {
            // 发现质数 i
            // 去筛掉 L到R 之间的所有合数
            gao(i);
        }
        for (int j = i; j <= r / j; j += i)
        {
            // 标记i的倍数
            v[j] = i;
        }
    }
    int z = 0;
    for (int i = 0; i <= r - l; i++)
    {
        // f[i] 表示 l+i 是不是合数
        // 如果 f[i]==0 说明 l+i 是质数
        if (!f[i])
        {
            z++;
        }
    }
    printf("%d\n", z);
    return 0;
}

P3601 签到题

欧拉函数
phi(x) 从1到x有多少个数字和x互质

n = a1 ** b1 * a2 ** b2 * ... * ak ** bk
phi(n) = n * (1 - 1/a1) * (1 - 1/a2) * .. * (1 - 1/ak)

360 = 2**3 * 3**2 * 5**1
phi(360) = 360 * (1-1/2) * (1-1/3) * (1-1/5) = 96

1到x有多少个数字与x不互质 x-phi(x)

#include <bits/stdc++.h>
using namespace std;
const int mod = 666623333;
long long a[1000020]; // a[i] 初始是 l+i 如果发现了质因数,就从中约掉,最后剩下什么
long long f[1000020]; // f[i] = phi(i + l)
long long l, r;
int v[1000020];
void gao(int p)
{
    for (long long i = (l + p - 1) / p * p; i <= r; i += p)
    {
        while (a[i - l] % p == 0) // 无论质因数 p 出现多少次,都约掉
        {
            a[i - l] /= p;
        }
        f[i - l] = f[i - l] / p * (p - 1); // 无论质因数 p 出现多少次,只做一次 / p * (p - 1)
    }
}
long long phi(long long x)
{
    int re = x;
    for (long long i = 2; i * i <= x; i++)
    {
        if (x % i == 0)
        {
            re = re / i * (i - 1);
            while (x % i == 0)
            {
                x /= i;
            }
        }
    }
    if (x > 1)
    {
        re = re / x * (x - 1);
    }
    return re;
}
int main()
{
    scanf("%lld%lld", &l, &r);
    for (int i = 0; i <= r - l; i++)
    {
        a[i] = l + i;
        f[i] = l + i;
    }
    for (int i = 2; i <= r / i; i++)
    {
        if (!v[i])
        {
            gao(i);
        }
        for (int j = i; j <= r / j; j += i)
        {
            v[j] = i;
        }
    }
    long long z = (l + r) % mod * (r - l + 1) % mod * 333311667 % mod;
    // l + l+1 + ... + r-1 + r
    for (int i = 0; i <= r - l; i++)
    {
        if (a[i] > 1) // 约掉所有 <= sqrt(r) 的质数之后,如果剩下的数字不是1,那么一定是质数
        {
            f[i] = f[i] / a[i] * (a[i] - 1); // 修改 f[i]
        }
        z = (z - f[i]) % mod;
    }
    printf("%lld\n", (z + mod) % mod);
    return 0;
}

spoj FACTCG2

https://www.spoj.com/problems/FACTCG2/

spoj SITB

https://www.spoj.com/problems/SITB/

spoj GCDS

spoj SMALLM

  1. 筛法
    1. Sieve of Eratosthenes
    2. Euler's sieve
    3. 最小质因数
    4. P3383 【模板】线性筛素数
    5. P1835 素数密度
    6. P3601 签到题
    7. spoj FACTCG2
    8. spoj SITB
    9. spoj GCDS
    10. spoj SMALLM