zoukankan      html  css  js  c++  java
  • 对积性函数筛法的理解

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

    对于函数(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])的倍数时,所维护值的一个更新情况
  • 相关阅读:
    MySQL的事务和视图
    MySQL中的常用函数
    高级查询(一)
    街道管理系统
    深入.NET平台和C#编程的错题
    appium python下的API方法
    Charles 使用教程
    appium,iOS下,ios_predicate等定位方式
    Linux -常用命令
    python-列表以及相关函数认识
  • 原文地址:https://www.cnblogs.com/Emcikem/p/13897128.html
Copyright © 2011-2022 走看看