对积性函数筛法的理解

积性函数和完全积性函数在欧拉筛里的情况

对于函数(f(x))
然后式子(f(x y) = f(x) * f(y))
如果说只有x和y互质的时候才成立,那么(f(x))是一个积性函数
如果说对于任意的x和y都成立,那么(f(x))是一个完全积性函数

在线性筛里面,如果说(f(x))是一个积性函数,那么

void pri_table(int N){
    vis[1] = vis[0] = 1;
    F[1] = cal(1);
    for(int i = 2; i <= N; i++) {
        if(!vis[i]) {
            pri[++tot] = i;
            F[i] = cal(i);
        }
        for(int j = 1; j <= tot; j++) {
            if(i * pri[j] > N) break;
            vis[i * pri[j]] = 1;
            if(i % pri[j] == 0) {
                  //考虑维护质数时的值更新即可
                break;
            }else F[i * pri[j]] = F[i] * F[pri[j]];//积性函数情况
        }
    }
}

如果是一个完全积性函数,那么可以直接筛

void pri_table(int N){
    vis[1] = vis[0] = 1;
    F[1] = cal(1);
    for(int i = 2; i <= N; i++) {
        if(!vis[i]) {
            pri[++tot] = i;
            F[i] = cal(i);
        }
        for(int j = 1; j <= tot; j++) {
            if(i * pri[j] > N) break;
            vis[i * pri[j]] = 1;
            F[i * pri[j]] = F[i] * F[pri[j]];
            if(i % pri[j] == 0) break;//维护质数时的值更新
        }
    }
}

题目

比如题目传送门
其中(f(x) = x ^ n)是一个完全积性函数
(f(xy) = (xy) ^ n = x^n y^n)
那么就利用(F[i * pri[j]] = F[i] * F[pri[j]])即可
对于质数就直接利用快速幂求
素数个数(frac{1.5n}{logn})左右,每个素数的快速幂是(O(logn))那么时间复杂度就是(O(n))

#include <iostream>
#include <cstdio>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e7 + 5;
const int mod = 1e9 + 7;
int pri[N], tot;
bool vis[N];
ll F[N];
ll pow(ll a, ll b, ll p){
    ll ans = 1; a %= p;
    while(b){
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}
void pri_table(int N, int n){
    vis[1] = vis[0] = 1;
    F[1] = 1;
    for(int i = 2; i <= N; i++) {
        if(!vis[i]) {
            pri[++tot] = i;
            F[i] = pow(i, n, mod);
        }
        for(int j = 1; j <= tot; j++) {
            if(i * pri[j] > N) break;
            vis[i * pri[j]] = 1;
            F[i * pri[j]] = (ll)F[i] * F[pri[j]] % mod;
            if(i % pri[j] == 0) break;
        }
    }
}
int main(){
    int n;
    scanf("%d", &n);
    pri_table(N - 3, n);
    ll ans = 0;
    for(int i = 1; i <= n; i++) 
        ans = ans ^ F[i];
    printf("%lld
", ans);

    return 0;
}

对于题目传送门
也是一个线性筛问题,其实(f(n,kk))表示把一个数(n)划分为(kk)个数字乘积的个数
根据唯一分解定理(n = p_1^{a_1} p_2^{a_2}...p_k^{a_k} = prod_{i = 1}^kp_i^{a_i})
那么就是说(f(n,kk) = prod_{i = 1}^k g(a_i, kk))
(g(a_i, kk))表示把(a_i)划分为(kk)个数字相加的情况数。
因为(p_i)都是质数,那么也就是每一个质数都是独立的,因为几个质数的乘积不能用另外几个质数的乘积来表示
比如(a_1)的一组方案(p_1^1 imes p_1^2 imes p_1 ^ 3)(a_2)的一组方案(p2^2 imes p_2^1 imes p_3^0)
那么他们组合就是((p_1^1 p_2^3) imes (p_1^2 p_2^1) imes (p_1^3 p_2^0)), 总共的方案数就是两个的方案数的乘积

转换为模型(sum_{i = 1}^kx_i = t)的非负整数解的个数

[g(t, k) = left(egin{array}{c} t+k-1 \ k-1 end{array} ight) ]

如果是求一个数,那么直接对数字质因子分解,然后求组合数,再乘起来就行了
但是对于n个数字,就需要采用线性筛
把x拆分为(x = y imes p^q)
其中(gcd(y, p) = 1),且p是素数
那么(f(x, k) = f(y, k) imes f(p^q, k) = f(y, k) imes left(egin{array}{c} q+k-1 \ k-1 end{array} ight))
其中(f(p, k) = k),(p)是质数

那么对于质数倍数时的更新从(C_{num + k - 1} ^ {k - 1}) 变成了 (C_{num + k} ^ {k - 1})
化简后得到值扩大了(frac{num + k}{num + 1})倍,其实num表示数字含有的质数个数
那么直接线性筛就行了

#include <iostream>
#include <cmath>
#include <cstdio>
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e7 + 5;
int pri[N], num[N], tot;
ll F[N], inv[N];
bool vis[N];
void pri_table(int N, ll k){
    F[1] = 1, inv[1] = 1;
    for(int i = 2; i <= N; i++) 
        inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;
    for(int i = 2; i <= N; i++) {
        if(!vis[i]) {
            pri[++tot] = i;
            num[i] = 1;
            F[i] = k;
        }
        for(int j = 1; j <= tot; j++) {
            if(i * pri[j] > N) break;
            vis[i * pri[j]] = 1;
            if(i % pri[j] == 0) {
                num[i * pri[j]] = num[i] + 1;
                F[i * pri[j]] = F[i] * (num[i] + k) % mod * inv[num[i] + 1] % mod;
                break;
            }else {
                num[i * pri[j]] = 1;
                F[i * pri[j]] = F[i] * k % mod;
            }
        }
    }
}
int main(){
    int n; ll k; scanf("%d%lld", &n, &k);
    k %= mod;
    pri_table(N - 3, k);
    ll ans = 0;
    for(int i = 1; i <= n; i++)
        ans ^= (F[i] + i) % mod;
    printf("%lld
", ans);
    return 0;
}

其中逆元的个数可以优化,减少空间,因为逆元最大是(num[i] + 1),也就是(2^n),n的值也就在32以内

总结

  • 注意函数是积性函数还是完全积性函数,因为两种函数在欧拉筛里放的位置不同
  • 注意当(i)(pri[i])的倍数时,所维护值的一个更新情况
原文地址:https://www.cnblogs.com/Emcikem/p/13897128.html