Skip to content

整除分块

又叫数论分块。

ni 只有 O(n) 种数值,不同数值之间连续,且容易计算:[li,ri]nli,ri=nni,li=ri1+1

对于求和式:S(n)=i=1nf(i)g(ni),根据整除分块可得:S(n)=i=1mg(li)j=lirif(j),其中 m=O(n),总时间复杂度:O(nT(n)),其中 T(n) 为计算 f(n) 前缀和的时间复杂度。

cpp
for (int i = 2, j; i <= n; i = j + 1) {
    j = n / (n / i);
    // 求 [i, j] 的贡献
}

多维整除分块

形如:

i=1nj=1mFj(aji)

此时,分块区间 li=min{aji}

容易发现每个 aj 产生 O(aj) 个区间,总时间复杂度为:O(mn),但是区间有相同部分,常数较小。

cpp
for (int i = 1; i <= m; i++)
    maxn = max(maxn, a[i]);
for (int i = 2, j; i <= maxn;i i = j + 1) {
    j = maxn;
    for (int k = 1; k <= m; k++) {
        j = min(j, a[k] / (a[k] / i));
    }
    // 求 [i, j] 的贡献
}

二次整除分块

形如:

i=1nni2

时间复杂度:O(n3)

cpp
for (int i = 1, j; i <= m; i = j + 1) {
    auto now = n / (i * i);
    j = sqrtl(n / now);
   	// 求 [i, j] 的贡献
}

杜教筛

杜教筛用于求解一些积性函数前缀和:S(n)=i=1nf(i)

构造积性函数 g(n)

i=1n(fg)(n)=i=1ndif(d)g(id)=i=1ng(d)S(nd)

特别地:

g(1)S(n)=i=1n(fg)(n)i=2ng(d)S(nd)

T(n) 为求解 S(n) 的时间复杂度,结合整除分块,那么:

T(n)=H(n)+dnnG(d)T(d)

预处理 S(1)S(m),时间复杂度:O(m+nm)m=n23 时最优,为:O(n23)

递归过程中状态需要记忆化,通常使用哈希表维护。

特别地,若求一次 S(n),容易发现状态只有 O(n) 个,分为 [1,n](n,n] 分别储存即可。

cpp
int sum_euler(int n) {
    if (n <= 2e6)
        return euler[n];
    if (mem1.count(n))
        return mem1[n];
    int res = (1 + n) * n / 2;
    for (int i = 2, j; i <= n;) {
        j = n / (n / i);
        res -= (j - i + 1) * sum1(n / i);
        i = j + 1;
    }
    return mem1[n] = res;
}
int sum_mu(int n) {
    if (n <= 2e6)
        return mu[n];
    if (mem2.count(n))
        return mem2[n];
    int res = 1;
    for (int i = 2, j; i <= n;) {
        j = n / (n / i);
        res -= (j - i + 1) * sum2(n / i);
        i = j + 1;
    }
    return mem2[n] = res;
}

min_25 筛