筛法

首先想到筛法,筛的肯定是素数
显然,埃氏筛,欧拉筛(线性筛)都是为了求出素数而产生的
再联想一个东西——积性函数
满足为\(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);
    } 
}
posted @ 2022-10-18 19:36  紫飨  阅读(9)  评论(2编辑  收藏  举报