积性函数及其筛法

积性函数及其筛法

前置知识

一些定理

质数筛法 - Cattle_Horse

互质:公约数只有 1 的两个整数叫做互质整数,也称这两个整数互质

算术函数(数论函数):定义在所有正整数集上的函数称为算术函数(数论函数)

积性函数(乘性函数):对于任意互质的整数 an 有性质 f(a×b)=f(a)×f(b) 的数论函数

完全积性函数:对于任意整数 ab 有性质f(a×b)=f(a)×f(b)的数论函数

算术基本定理(唯一分解定理):任何一个大于 1 的自然数 N,如果 N 不为质数,那么 N 可以唯一分解成有限个质数的乘积 N=P1a1P2a2P3a3Pnan,这里 P1<P2<P3<Pn 均为质数,其中指数 ai 是正整数。这样的分解称为 N 的标准分解式。

积性函数的重要性质

  • f 为积性函数,则 f(1)=1
    证明:nN+ ,则有 gcd(n,1)=1,所以 f(n)=f(n)×f(1),因此 f(1)=1

  • (由算术基本定理推得)对于整数 n ,若 f 是积性函数,则f(n)=f(p1a1)×f(p2a2)××f(pnan)

  • f(x)g(x) 均为积性函数,则以下函数也为积性函数

    • h(x)=f(xp)
    • h(x)=fp(x)
    • h(x)=f(x)×g(x)
    • h(x)=dxf(d)×g(xd)

常见的积性函数

欧拉函数 φ(n):在数论中,对于正整数 n,欧拉函数是 1n 中与 n 互质的数的个数,即 φ(n)=x=1n[gcd(x,n)=1]

莫比乌斯函数 μ(i)μ(n)={1n=10n 含有平方因子 (1)kk 为 n 的本质不同质因子个数 

正整数 n 的约数(正因子)个数 d(n)σ0(n)σ0(n)=dn1

正整数 n 的所有约数(正因子)之和 σ1(n)σ1(n)=dnd

正整数 n 的所有约数(正因子)k 次幂之和 σk(n)σk(n)=dndk

常见的完全积性函数

常函数 I(n)I(n)=1

单位函数 id(n)id(n)=n

幂函数 idk(n)idk(n)=nk

元函数 ϵ(n)ϵ(n)=[n=1]

欧拉函数

定义

欧拉函数 φ(n):在数论中,对于正整数 n,欧拉函数是 1n 中与 n 互质的数的个数,即 φ(n)=x=1n[gcd(x,n)=1]

性质

性质一

p 是质数,则 φ(p)=p1

证明:

p 是质数,则 p 仅有 p1 两个正因子

1p 中 ,只有 p 含有正因子 p,因此 φ(p)=p1

性质二

p 是质数,则 φ(pk)=(p1)×pk1

证明(非严谨):

p 是质数,则在 1pk 中,仅有 p,2p,3p,,pk1×p 含因子 p

因此,φ(pk)=pkpk1=(p1)×pk1

性质三

欧拉函数是一个积性函数

即 若gcd(m,n)=1,则 φ(mn)=φ(m)φ(n)

证明略.

计算公式

φ(n)=n×i=1s(11pi)

注:欧拉函数仅由 n 和其质因子决定,与质因子次数无关

证明(用到以上三个性质及算术基本定理):

由算术基本定理 n=i=1Spiαi=p1α1p2α2psαs

φ(n)=i=1Sφ(piαi)=i=1spiαi1(pi1)=i=1Spiαi(11pi)=i=1Spiαi×i=1S(11pi)=n×i=1Spi1pi

求单个数的欧拉函数

根据计算公式,通过分解质因子求欧拉函数

static int getPhi(int n) {
    int ans = n;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            ans = ans / i * (i - 1);
            do n /= i; while (n % i == 0);
        }
    }
    if (n > 1) ans = ans / n * (n - 1);
    return ans;
}

筛欧拉函数

线性筛质数的时候,我们提到每个合数 m 都是被其最小的质因子筛去的。

pj 是当前遍历到的质数,则 m 通过 m=pj×i 筛去

  1. i 能被 pj 整除,则 pjim 的最小质因子,且 i 包含了 m 的所有质因子
    φ(m)=m×k=1spk1pk=pj×i×k=1spk1pk=pj×φ(i)
  2. i 不能被 pj 整除,则 ipj 是互质的
    φ(m)=φ(pj×i)=φ(pj)×φ(i)=(pj1)×φ(i)
static int[] primes, phi;
static boolean[] isPrime;
// 求1~n的欧拉函数
static int getPhi(int n) {
    primes = new int[(n + 1) / 2];
    phi = new int[n + 1];
    isPrime = new boolean[n + 1];
    int tot = 0;
    for (int i = 2; i <= n; ++i) isPrime[i] = true;
    phi[1] = 1;//1单独处理
    for (int i = 2; i <= n; ++i) {
        //如果i是质数
        if (isPrime[i]) {
            primes[tot++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
            isPrime[m] = false;
            //如果i能被primes[j]整除,则i包含了合数i*prime[j]的所有质因子
            if (i % primes[j] == 0) {
                phi[m] = primes[j] * phi[i];
                break;
            }
            //否则i与primes[j]是互质的
            phi[m] = phi[primes[j]] * phi[i];
            //也可以这么写
            //phi[m]] = (primes[j] - 1) * phi[i];
        }
    }
    return tot;
}

约数(正因子)个数

定义

正整数 n 的约数(正因子)个数 d(n)σ0(n)σ0(n)=dn1

计算公式

约数个数定理 - 百度百科

对于正整数 n ,由算术基本定理得:n=i=1Spiαi,则 σ0(n)=i=1S(αi+1)

注:约数个数只由其质因子次数决定

证明:

piαi 的约数有 pi0pi1,,piαi(αi+1)

由乘法原理(分步乘法)得:σ0(n)=i=1S(αi+1)

求单个数的约数个数

根据计算公式,通过分解质因子求约数个数

static int getSigma0(int n) {
    int ans = 1;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            int k = 1;
            while (n % i == 0) {
                ++k;
                n /= i;
            }
            ans *= k;
        }
    }
    if (n > 1) ans *= 2;
    return ans;
}

筛约数个数

记录最小质因子次数

定义 σ0[i] 表示 i 的约数个数,Kth[i] 表示 i 的最小质因子的次数(指数)

i 是质数,则 Kth[i]=1σ0[i]=2

pj 是当前遍历到的质数,则 m 通过 m=pj×i 筛去

  1. i 能被 pj 整除,则 pj 一定是 im 的最小质因子,此时 im 的差别仅为最小质因子次数多 1

    σ0[i]=(α1+1)×(α2+1)××(αs+1)

    σ0[m]=(α1+1+1)×(α2+1)××(αs+1)

    • Kth[m]=Kth[i]+1
    • σ0[m]=σ0[i]÷(Kth[i]+1)×(Kth[m]+1)=σ0[i]÷(Kth[i]+1)×(Kth[i]+2)
  2. i 不能被 pj 整除,则 i 不含质因子 pj,换言之,m 的最小质因子 pj 的指数为 1
    • Kth[m]=1
    • σ0[m]=σ0[i]×(1+1)=2×σ0[i]
static int[] primes, sigma0, Kth;
static boolean[] isPrime;
// 求1~n的约数个数
static int getSigma0(int n) {
    primes = new int[(n + 1) / 2];
    sigma0 = new int[n + 1];
    Kth = new int[n + 1];
    isPrime = new boolean[n + 1];
    int tot = 0;
    for (int i = 2; i <= n; ++i) isPrime[i] = true;
    sigma0[1] = 1;//1单独处理
    for (int i = 2; i <= n; ++i) {
        //如果i是质数
        if (isPrime[i]) {
            primes[tot++] = i;
            Kth[i] = 1;
            sigma0[i] = 2;
        }
        for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
            isPrime[m] = false;
            //如果i能被primes[j]整除,则i包含了合数m的所有质因子
            if (i % primes[j] == 0) {
                Kth[m] = Kth[i] + 1;
                sigma0[m] = sigma0[i] / (Kth[i] + 1) * (Kth[i] + 2);
                break;
            }
            //否则i不含质因子primes[j]
            Kth[m] = 1;
            sigma0[m] = 2 * sigma0[i];
            //也可以这么写
            //sigma0[m] = sigma0[primes[j]] * sigma0[i];
        }
    }
    return tot;
}

不记录最小质因子次数

Kth[i] 最小质因子次数只用于上述情况一求 σ0[m]

如果不通过 Kth[i]σ0[m] ,则可以大大减少运算次数以及所需空间

上述情况一( i 能被 pj 整除):

σ0[m]=(α1+1+1)×(α2+1)××(αs+1)=2×(α1+1)×(α2+1)××(αs+1)α1×(α2+1)××(αs+1)=2×σ0[i]α1×(α2+1)××(αs+1)=2×σ0[i]σ0[ipj]

static int[] primes, sigma0;
static boolean[] isPrime;
// 求1~n的约数个数
static int getSigma0(int n) {
    primes = new int[(n + 1) / 2];
    sigma0 = new int[n + 1];
    isPrime = new boolean[n + 1];
    int tot = 0;
    for (int i = 2; i <= n; ++i) isPrime[i] = true;
    sigma0[1] = 1;//1单独处理
    for (int i = 2; i <= n; ++i) {
        //如果i是质数
        if (isPrime[i]) {
            primes[tot++] = i;
            sigma0[i] = 2;
        }
        for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
            isPrime[i * primes[j]] = false;
            //如果i能被primes[j]整除,则i包含了合数m的所有质因子
            if (i % primes[j] == 0) {
                sigma0[m] = 2 * sigma0[i] - sigma0[i / primes[j]];
                break;
            }
            //否则i与不含质因子primes[j]
            sigma0[m] = 2 * sigma0[i];
            //也可以这么写
            //sigma0[m] = sigma0[primes[j]] * sigma0[i];
        }
    }
    return tot;
}

约数(正因子)之和

定义

正整数 n 的所有约数(正因子)之和 σ1(n)σ1(n)=dnd

计算公式

约数和定理 - 百度百科

对于正整数 n ,由算术基本定理得:n=i=1Spiαi,则 σ1(n)=i=1Sj=0αipij

求和部分可以使用等比数列求和公式计算

注:约数和只由其质因子及其质因子次数决定

证明:

piαi 的约数有 pi0pi1,,piαi

piαi 的约数和为 j=0αipij

由乘法原理(分步乘法)得:σ1(n)=i=1Sj=0αipij

例:12=22×31σ1(12)=(1+2+4)×(1+3)=7×4=28

求单个数的约数和

根据计算公式,通过分解质因子求约数和

static int getSigma1(int n) {
    int ans = 1;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            int sum = 1, p = 1;
            while (n % i == 0) {
                p *= i;
                sum += p;
                n /= i;
            }
            ans *= sum;
        }
    }
    if (n > 1) ans *= 1 + n;
    return ans;
}

一个数的幂的约数和(暂未经过数据测试)

97. 约数之和 - AcWing题库

即求 σ1(nk)

对于正整数 n ,由算术基本定理得:n=i=1Spiαi,则 σ1(nk)=i=1Sj=0αi×kpij

证明:

σ1(nk)=σ1((i=1Spiαi)k)=i=1Sσ1(piαi×k)=i=1Sj=0αi×kpij

static int getSigma1(int n, int k) {
    int ans = 1;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            int index = 0;
            while (n % i == 0) {
                n /= i;
                index += k;
            }
            int sum = 1;
            int p = 1;
            for (int j = 0; j < index; ++j) {
                p *= i;
                sum += p;
            }
            ans *= sum;
        }
    }
    if (n > 1) {
        int sum = 1;
        int p = 1;
        for (int i = 0; i < k; ++i) {
            p *= n;
            sum += p;
        }
        ans *= sum;
    }
    return ans;
}

求和部分可以使用等比数列求和公式 Sn=a1×1qn1q,则

σ1(nk)=i=1S(piαi×k+11pi1)

求幂部分可以使用快速幂加速求解

static int pow(int a, int b) {
    int ret = 1;
    while (b != 0) {
        if ((b & 1) == 1) ret *= a;
        b >>= 1;
        a *= a;
    }
    return ret;
}
static int getSigma1(int n, int k) {
    int ans = 1;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            int index = 1;
            while (n % i == 0) {
                n /= i;
                index += k;
            }
            int sum = (pow(i, index) - 1) / (i - 1);
            ans *= sum;
        }
    }
    if (n > 1) {
        int sum = (pow(n, 1 + k) - 1) / (n - 1);
        ans *= sum;
    }
    return ans;
}

如果使用等比数列求和公式且要取模,则需要求解逆元

筛的约数之和

记录最小质因子的幂的和式

定义 g[i] 表示 i 的最小质因子的幂的和式 ,即g[i]=k=0αipjk

σ1[i] 表示 i 的约数和

i 是质数,g[i]=σ1[i]=i+1

pj 是当前遍历到的质数,则 m 通过 m=pj×i 筛去

  1. i 能被 pj 整除,则 pjim 的最小质因子,此时 mm 的差别仅为最小质因子 pj 的次数多 1

    g[i]=pj0+pj1++pjαj

    g[m]=pj0+pj1++pjαj+pjαj+1

    σ1[i]=g[i]×k=2Sj=0αkpkj

    σ1[m]=g[m]×k=2Sj=0αkpkj

    • g[m]=pj0+pj1++pjαj+pjαj+1=1+pj×(pj0+pj1++pjαj)=1+pj×g[i]
    • σ1[m]=g[m]×k=2Sj=0αkpkj=g[m]×σ1[i]g[i]
  2. i 不能被 pj 整除,则 i 不含质因子 pj,换言之,m 的最小质因子 pj 的指数为 1
    • g[m]=1+pj
    • σ1[m]=g[m]×σ1[i]
static int[] primes, sigma1, g;
static boolean[] isPrime;
// 求1~n的约数和
static int getSigma1(int n) {
    primes = new int[(n + 1) / 2];
    sigma1 = new int[n + 1];
    g = new int[n + 1];
    isPrime = new boolean[n + 1];
    int tot = 0;
    for (int i = 2; i <= n; ++i) isPrime[i] = true;
    sigma1[1] = 1;//1单独处理
    for (int i = 2; i <= n; ++i) {
        //如果i是质数
        if (isPrime[i]) {
            primes[tot++] = i;
            g[i] = sigma1[i] = i + 1;
        }
        for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
            isPrime[m] = false;
            //如果i能被primes[j]整除,则i包含了合数i*prime[j]的所有质因子
            if (i % primes[j] == 0) {
                g[m] = 1 + primes[j] * g[i];
                sigma1[m] = sigma1[i] / g[i] * g[m];
                break;
            }
            //否则i不含质因子primes[j],即m的最小质因子prime[j]的指数为1
            g[m] = 1 + primes[j];
            sigma1[m] = g[m] * sigma1[i];
            //也可以这么写
            //sigma1[m] = sigma1[primes[j]] * sigma1[i];
        }
    }
    return tot;
}

不记录最小质因子的幂的和式

如果不通过 g[i]σ1[m] ,则可以大大减少运算次数以及所需空间

上述情况一( i 能被 pj 整除):

考虑 σ1(i)σ1(ipj)σ1(m) 的关系

T=k=2Sj=0αkpkj,则

σ1[i]=(pj0+pj1++pjαj)×k=2Sj=0αkpkj=(pj0+pj1++pjαj)×T(1)σ1[m]=(pj0+pj1++pjαj+1)×k=2Sj=0αkpkj=σ1[i]+pjαj+1×T(2)σ1[ipj]=(pj0+pj1++pjαj1)×k=2Sj=0αkpkj=σ1[i]pjαj×T(3)

pj×(3)+(2),得:

σ1[m]+pjσ1[ip1]=(pj+1)×σ1[i]

整理,得

σ1[m]=(pj+1)×σ1[i]pj×σ1[ipj]

static int[] primes, sigma1;
static boolean[] isPrime;
// 求1~n的约数和
static int getSigma1(int n) {
    primes = new int[(n + 1) / 2];
    sigma1 = new int[n + 1];
    isPrime = new boolean[n + 1];
    int tot = 0;
    for (int i = 2; i <= n; ++i) isPrime[i] = true;
    sigma1[1] = 1;//1单独处理
    for (int i = 2; i <= n; ++i) {
        //如果i是质数
        if (isPrime[i]) {
            primes[tot++] = i;
            sigma1[i] = i + 1;
        }
        for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
            isPrime[m] = false;
            //如果i能被primes[j]整除,则i包含了合数i*prime[j]的所有质因子
            if (i % primes[j] == 0) {
                sigma1[m] = (1 + primes[j]) * sigma1[i] - primes[j] * sigma1[i / primes[j]];
                break;
            }
            //否则i不含质因子primes[j],即m的最小质因子prime[j]的指数为1
            sigma1[m] = (1 + primes[j]) * sigma1[i];
            //也可以这么写
            //sigma1[m] = sigma1[primes[j]] * sigma1[i];
        }
    }
    return tot;
}

求连续区间的数的约数和的和

即求 i=lrσ1(i)

可不通过筛法求解,而通过数论分块(整除分块)的方法在 O(rl+1) 的时间复杂度下求解

具体解释见 洛谷 P1403 约数研究 - Cattle_Horse

import java.util.Scanner;

public class Main {
    //计算 1~n 所有约数的和
    static long calculate(long n) {
        long ans = 0;
        for (long l = 1, r; l <= n; l = r + 1) {
            r = Math.min(n / (n / l), n);
            ans += (l + r) * (r - l + 1) / 2 * (n / l);
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt(), y = sc.nextInt();
        System.out.println(calculate(y) - calculate(x - 1));
    }
}

约数(正因子)k次幂之和

定义

正整数 n 的所有约数(正因子)k 次幂之和 σk(n)σk(n)=dndk

计算公式

对于正整数 n ,由算术基本定理得:n=i=1Spiαi,则 σk(n)=i=1Sj=0αipij×k

证明:

对于质数 ppα 的约数有 p0,p1,,pα,则 σk(pα)=i=0αpi×k

又由算术基本定理及积性函数的性质得:

σk(n)=σk(i=1Spiαi)=i=1Sσk(piαi)=i=1Sj=0αipij×k

求单个数的约数(正因子)k次幂之和

根据计算公式,通过分解质因子求约数(正因子)k 次幂之和

幂次部分可以使用快速幂求解

static final int MOD = (int) 1e9 + 7;
static int pow(int a, int b) {
    long ans = 1;
    while (b != 0) {
        if (b % 2 == 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b /= 2;
    }
    return (int) ans;
}
static int getSigmaK(int n, int k) {
    int ans = 1;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            long sum = 1;
            int index = 0;
            while (n % i == 0) {
                n /= i;
                index += k;
                sum = (sum + pow(i, index)) % MOD;
            }
            ans = (int) ((long) ans * sum % MOD);
        }
    }
    if (n > 1) {
        long sum = (pow(n, k) + 1) % MOD;
        ans = (int) (ans * sum % MOD);
    }
    return ans;
}

筛约数(正因子)k次幂之和

σk(n)=i=1Sj=0αipij×k

定义 i 为当前遍历到的数

i 为质数,则 σk(i)=j=01ij×k=ik+1

pj 是当前遍历到的质数,m=ipj

  1. pj 能整除 i,则 pjim 的最小质因子,即 m 仅是最小质因子 pj 的指数比 i1

    考虑 σk(i)σk(ipj)σk(m) 的关系

     T=x=2Sy=0αxpxy×kσk(i)=(y=0α1pjy×k)×(x=2Sy=0αxpxy×k)=T×y=0α1pjy×k(1)σk(ipj)=(y=0α11pjy×k)×(x=2Sy=0αxpxy×k)=σk(i)T×pjα1×k(2)σk(m)=(y=0α1+1pjy×k)×(x=2Sy=0αxpxy×k)=σk(i)+T×pjα1×k×pjk(3)

    pjk×(2)+(3) 得 :σk(ipj)×pjk+σk(m)=(pjk+1)×σk(i)

    整理,得:σk(m)=(pjk+1)×σk(i)σk(ipj)×pjk

    因此需要预处理出 pjk 的值,可以使用快速幂求得

  2. pj 不能整除 i,则 pji 互质,换言之,m 的最小质因子 pj 的指数为 1

    σk(m)=σk(i)×(pjk+1)=σk(i)×σk(pj)

    PS:所以它是积性函数

static long pow(long a, int b) {
    long ans = 1L;
    while (b != 0) {
        if (b % 2 == 1) ans = ans * a;
        a = a * a;
        b >>= 1;
    }
    return ans;
}
static int[] primes;
static boolean[] isPrime;
static long[] pk;
static long[] sigmaK;
// 求1~n的约数(正因子)k次幂之和
public static int getSigmaK(final int n, final int k) {
    int len = (int) Math.max((n + 1) / (Math.log(n + 1) - 1.112), 1);
    primes = new int[len];
    isPrime = new boolean[n + 1];
    Arrays.fill(isPrime, true);
    isPrime[0] = isPrime[1] = false;
    pk = new long[n + 1];
    sigmaK = new long[n + 1];
    sigmaK[1] = pk[1] = 1;//1单独处理
    int tot = 0;
    for (int i = 2; i <= n; ++i) {
        if (isPrime[i]) {
            primes[tot++] = i;
            pk[i] = pow(i, k);
            sigmaK[i] = pk[i] + 1;
        }
        for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
            isPrime[m] = false;
            pk[m] = pk[primes[j]];
            if (i % primes[j] == 0) {
                sigmaK[m] = (pk[primes[j]] + 1) * sigmaK[i] - sigmaK[i / primes[j]] * pk[i];
                break;
            }
            sigmaK[m] = sigmaK[i] * (pk[primes[j]] + 1);
            // 也可以这么写
            // sigmaK[m] = sigmaK[i] * sigmaK[primes[j]];
        }
    }
    return tot;
}

参考资料

数论学习笔记1之积性函数与线性筛求积性函数_cqust_qilin02811的博客

积性函数与筛法 - 又又大柚纸

515 筛法求欧拉函数 - 董晓算法

516 筛法求约数个数 - 董晓算法

517 筛法求约数和 - 董晓算法

线性筛约数个数、约数和的新方法 - ldysy2102

约数之和 (数论) - 基地AI

浅谈一类积性函数的前缀和_skywalkert的博客

posted @   Cattle_Horse  阅读(86)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示