忘掉过去的人,终将重蹈覆辙。|

chenwenmo

园龄:6个月粉丝:0关注:7

学习笔记:莫比乌斯函数

前置知识:迪利克雷卷积,整除分块。


莫比乌斯函数

μ(n)={1if n=1,0if n 有平方因子,(1)kk=n 的质因子个数.

积性函数,即 gcd(a,b)=1 时,μ(a)μ(b)=μ(ab)

基本性质

性质 1

莫比乌斯函数是数论函数 1(n)=1 的迪利克雷逆,即 μ1=ϵ

证明在性质 2。

性质 2

dnμ(d)=ϵ(n)

证明

n=p1c1p2c2 ... pkckn=p1p2 ... pkp 都是质数,就是唯一分解定理,

dnμ(d)=dnμ(d),因为只有次数为 1 的质数才有贡献,不然就有平方因子,贡献为 0

dnμ(d)=i=0k(ik)(1)i

然后根据二项式定理,i=0k(ik)(1)i=(1+(1))k

因此当且仅当 k=0n=1 时,dnμ(d)=1,否则为 0

同时也证明了性质 1。

该性质有一些比较常见的运用,例如,

  • [gcd(i,j)=1]=dgcd(i,j)μ(d)

性质 3 ( 莫比乌斯变换 / 逆变换(反演) )

一般有两个形式。

形式 1

f(n)=dng(n),则 g(n)=dnμ(d)f(n/d)

我们称 f(n)g(n) 的莫比乌斯变换,g(n)f(n) 的莫比乌斯反演。

证明

dnμ(d)f(n/d)=dnμ(d)kndg(k)=kng(k)dnkμ(d)=g(n)

或者用迪利克雷卷积证明,

f(n)=dng(n)f=g1g=fμg(n)=dnμ(d)f(n/d)

形式 2

f(n)=ndg(d),则 g(n)=ndμ(d/n)f(d)

证明

ndμ(d/n)f(d)=k=1infμ(k)f(kn)=k=1infμ(k)kndg(d)=ndg(d)kdnμ(k)=g(n)

练习

P3455 [POI2007] ZAP-Queries

Solution

n<m,由题意,

i=1nj=1m[gcd(i,j)=d]

=i=1n/dj=1m/d[gcd(i,j)=1]

=i=1n/dj=1m/dkgcd(i,j)μ(k)

=k=1nμ(k)(n/kd)(m/kd)

根据整除分块以及整除点的性质可知,使得 (n/kd)(m/kd) 不同的 k 的个数只有 O(n) 个。于是先 O(n) 预处理 μ 的前缀和,然后遍历 nm 的整除点,计算贡献即可。

这段也可以用莫比乌斯反演来推。

有一个常见的技巧,互质不方便直接判断,但是判断 ijgcd 是不是某个数 k 的倍数是简单的,只需要 k 同时是 ij 的约数。

f(d)=i=1nj=1m[gcd(i,j)=d]

g(k)=kdf(d)

容易得到 g(k)=nkmk

根据莫比乌斯反演形式 2,f(k)=kdμ(dk)g(d)

枚举 x=dk,于是 f(k)=x=1nμ(x)g(xk)=x=1nμ(x)nxkmxk

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 5e4 + 5;

int n, m, d;
int mob[N], prime[N], vis[N], cnt;
ll sum[N];

void Solve() {
    cin >> n >> m >> d;
    n /= d, m /= d;
    ll ans = 0, l1 = 1, r1 = 1, l2 = 1, r2 = 1;
    while (l1 <= n && l2 <= m) {
        r1 = n / (n / l1);
        r2 = m / (m / l2);
        ans += (sum[min(r1, r2)] - sum[max(l1, l2) - 1]) * (n / r1) * (m / r2);
        // cout << l1 << ' ' << r1 << ' ' << l2 << ' ' << r2 << '\n';
        if (r1 <= r2) l1 = r1 + 1;
        if (r1 >= r2) l2 = r2 + 1;
    }
    cout << ans << '\n';
}

int main() {
    cnt = 0;
    mob[1] = sum[1] = 1;
    for (int i = 2; i <= 5e4; i++) {
        if (!vis[i]) {
            prime[++cnt] = i;
            vis[i] = i;
            mob[i] = -1;
        }
        sum[i] = sum[i - 1] + mob[i];
        for (int j = 1; j <= cnt && prime[j] <= 5e4 / i; j++) {
            vis[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) {
                mob[i * prime[j]] = 0;
                break;
            } else {
                mob[i * prime[j]] = -mob[i];
            }
        }
    }

    int T;
    cin >> T;
    while (T--) Solve();
    return 0;
}

P2257 YY的GCD

Solution

n<m,由题意,

i=1nj=1m[gcd(i,j)prime]

=kprimei=1nj=1m[gcd(i,j)=k]

=kprimei=1n/kj=1m/k[gcd(i,j)=1]

=kprimei=1n/kj=1m/kdgcd(i,j)μ(d)

=kprimed=1nμ(d)n/kdm/kd

x=kd,则,

原式 =kprimed=1nμ(d)n/xm/x

考虑枚举 x,则,

原式 =x=1nn/xm/xkx,kprimeμ(x/k)

前一半 O(n) 整除分块,后一半可以近似 O(n) 预处理。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 1e7 + 5;

int n, m;
int prime[N], cnt, vis[N], mob[N];
ll res[N], sum[N];

void Solve() {
    cin >> n >> m;
    int l1 = 1, r1, l2 = 1, r2;
    ll ans = 0;
    while (l1 <= n && l2 <= m) {
        r1 = n / (n / l1);
        r2 = m / (m / l2);
        ans += (1ll * n / r1) * (1ll * m / r2) * (sum[min(r1, r2)] - sum[max(l1, l2) - 1]);
        if (r1 <= r2) l1 = r1 + 1;
        if (r2 <= r1) l2 = r2 + 1;
    }
    cout << ans << '\n';
}

int main() {
    mob[1] = 1;
    for (int i = 2; i <= 1e7; i++) {
        if (!vis[i]) {
            prime[++cnt] = i;
            vis[i] = i;
            mob[i] = -1;
        }
        for (int j = 1; j <= cnt && prime[j] <= 1e7 / i; j++) {
            vis[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) {
                mob[prime[j] * i] = 0;
                break;
            } else {
                mob[prime[j] * i] = -mob[i];
            }
        }
    }
    for (int i = 1; i <= cnt; i++) {
        for (int j = prime[i]; j <= 1e7; j += prime[i]) {
            res[j] += (ll)mob[j / prime[i]];
        }
    }
    for (int i = 1; i <= 1e7; i++) {
        sum[i] = sum[i - 1] + res[i];
    }

    int T;
    cin >> T;
    while (T--) Solve();
    return 0;
}

P3312 [SDOI2014] 数表

Solution

n<m,由题意,

i=1nj=1mσ1(gcd(i,j))

其中 σ1(n) 是约数和函数。

原式 =d=1ni=1nj=1mσ1(d)×[gcd(i,j)=d]

=d=1nσ1(d)i=1n/dj=1m/d[gcd(i,j)=1]

=d=1nσ1(d)i=1n/dj=1m/dkgcd(i,j)μ(k)

=d=1nσ1(d)k=1nμ(k)nkdmkd

x=kd,则,

原式 =d=1nσ1(d)k=1nμ(k)nxmx

=x=1nnxmxdxσ0(d)μ(xd)

如果没有限制 a,那么这样就已经做完了,整除分块加上预处理。如果加上限制 a 的话,那么最终式子中的 σ0(d) 只有 a 才会产生贡献。

那么我们考虑离线处理,按照询问的 a 升序排序,然后把预处理出来的 σ0(d) 也按照升序排序,对于每个询问,我们都把 aσ0(d) 枚举它的倍数预处理。

因此需要实时更改前缀和,于是树状数组维护即可。时间复杂度 O(qnlogn+nlnnlogn)=O(能过)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 1e5 + 5;
const ll mod = 1ll << 31;

int q;
ll ans[N];
ll prime[N], vis[N], mob[N], c[N], p[N]; int cnt;
// c[i] 是 i 的最小质因子的次数。
// p[i] 是 i 的最小质因子的 c[i] 次方。

struct Info {
    int n, m, idx;
    ll a;
    bool operator < (const Info &_) const { return a < _.a; }
} A[N];

struct Sigma { // σ1函数
    ll val, x;
    bool operator < (const Sigma &_) const { return val < _.val; }
} sig[N];

struct BitTree {
    int n;
    vector<ll> a;

    void init(int _n) {
        n = _n;
        a.assign(n + 5, 0);
    }

    void add(int x, ll v) {
        for (; x <= n; x += (x & -x)) a[x] += v;
    }

    ll sum(int x) {
        ll ans = 0;
        for (; x; x -= (x & -x)) ans += a[x];
        return ans;
    }

    ll sum(int l, int r) {
        return sum(r) - sum(l - 1);
    }
} t;

ll work(int n, int m) {
    int l = 1, r;
    ll ans = 0;
    if (n > m) swap(n, m);
    for (; l <= n; l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        ans += t.sum(l, r) * (1ll * n / l) * (1ll * m / l);
    }
    return ans;
}

int main() {
    mob[1] = 1;
    sig[1] = {1, 1};
    for (int i = 2; i <= 1e5; i++) {
        if (!vis[i]) {
            vis[i] = i;
            prime[++cnt] = i;
            mob[i] = -1;
            sig[i] = {i + 1, i};
            c[i] = 1;
            p[i] = i;
        }
        for (int j = 1; j <= cnt && prime[j] <= 1e5 / i; j++) {
            vis[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) {
                mob[prime[j] * i] = 0;
                c[prime[j] * i] = c[i] + 1;
                p[prime[j] * i] = p[i] * prime[j];
                if (p[i] == i) {
                    sig[prime[j] * i] = {sig[i].val + p[i] * prime[j], prime[j] * i};
                } else {
                    sig[prime[j] * i] = {sig[i / p[i]].val * sig[p[i] * prime[j]].val, prime[j] * i};
                }
                break;
            } else {
                c[prime[j] * i] = 1;
                p[prime[j] * i] = prime[j];
                mob[prime[j] * i] = -mob[i];
                sig[prime[j] * i] = {sig[i].val * sig[prime[j]].val, prime[j] * i};
            }
        }
    }
    sort(sig + 1, sig + 1 + (int)1e5);

    t.init(1e5);

    cin >> q;
    for (int i = 1; i <= q; i++) {
        cin >> A[i].n >> A[i].m >> A[i].a;
        A[i].idx = i;
    }
    sort(A + 1, A + 1 + q);
    for (int i = 1, j = 1; i <= q; i++) {
        while (j <= 1e5 && sig[j].val <= A[i].a) {
            for (int k = sig[j].x; k <= 1e5; k += sig[j].x) {
                t.add(k, sig[j].val * mob[k / sig[j].x]);
            }
            j++;
        }
        ans[A[i].idx] = work(A[i].n, A[i].m);
    }
    for (int i = 1; i <= q; i++) cout << ans[i] % mod << '\n';
    return 0;
}

P3327 [SDOI2015] 约数个数和

Solution

重要性质:σ0(ij)=xiyj[gcd(x,y)=1]

本证明参考了该篇题解

证明:

kijk=p1c1p2c2...ptct

那么对于 k 的每个质因子 pi,我们要保证 ip 的次数为 ajp 的次数为 b,且 a+bci,

通俗来说,相当于可以从 i 中拿了 ap 出来,j 中拿了 bp 出来,满足 a+b=ci

那么我们钦定 i 中如果 a=p,也就是直接能拿够就直接拿,否则在 j 中拿剩下的 b=pa 个,

因此不会同时从 ij 拿出相同的质因子,所以 gcd(x,y)=1,代表从 i 中拿出了 x,从 j 中拿出了剩余不够的 y,凑成了一个 ij 的因子 k

i=1nj=1mσ0(ij)

=i=1nj=1mxiyj[gcd(x,y)=1]

=x=1ny=1mnxmy[gcd(x,y)=1]

f(d)=x=1ny=1mnxmy[gcd(x,y)=d]

g(d)=dkf(k)

易知,g(d)=x=1ny=1mnxmy[dgcd(x,y)]

将限制条件同时除去 d,得,g(d)=x=1ndy=1mdndxmdy

由莫比乌斯反演,得,f(d)=dkμ(kd)g(k)

h(x)=i=1xxiO(nn) 即可预处理,

易知,g(d)=h(nd)h(md),于是便能快速算出 g 函数,

由题意,最终答案即 f(1)

f(1)=1kμ(k)g(k)=k=1nμ(k)g(k)=k=1nμ(k)h(nk)h(mk)

于是整除分块即可。

时间复杂度 O(nn)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 5e4 + 5;

int n, m;
ll prime[N], vis[N], mob[N]; int cnt;
ll sum[N], h[N];

void Solve() {
    cin >> n >> m;
    if (n > m) swap(n, m);
    ll ans = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        ans += (sum[r] - sum[l - 1]) * h[n / l] * h[m / l];
    }
    cout << ans << '\n';
}

int main() {
    sum[1] = mob[1] = 1;
    for (int i = 2; i <= 5e4; i++) {
        if (!vis[i]) {
            vis[i] = i;
            prime[++cnt] = i;
            mob[i] = -1;
        }
        for (int j = 1; j <= cnt && prime[j] <= 5e4 / i; j++) {
            vis[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) {
                mob[prime[j] * i] = 0;
                break;
            } else {
                mob[prime[j] * i] = -mob[i];
            }
        }
        sum[i] = sum[i - 1] + mob[i];
    }

    for (int i = 1; i <= 5e4; i++) {
        for (int l = 1, r; l <= i; l = r + 1) {
            r = i / (i / l);
            h[i] += (1ll * i / l) * (r - l + 1);
        }
    }

    int T;
    cin >> T;
    while (T--) Solve();
    return 0;
}

P1829 [国家集训队] Crash的数字表格 / JZPTAB

Solution

i=1nj=1mijgcd(i,j)

=d=1ni=1nj=1mijd[gcd(i,j)=d]

=d=1ni=1ndj=1mdijd×d2×[gcd(i,j)=1]

=d=1ni=1ndj=1mdijd[gcd(i,j)=1]

=d=1ndi=1ndj=1mdijkgcd(i,j)μ(k)

=d=1ndk=1ndμ(k)ki,indikj,jmdj

=d=1ndk=1ndμ(k)i=1ndkikj=1mdkjk

a(x)=i=1xi

则 原式 =d=1ndk=1ndμ(k)k2a(ndk)a(mdk)

f(n,m)=k=1nμ(k)k2a(nk)a(mk)

然后预处理 a 以及前缀和 i=1nμ(i)i2,进行整除分块,即,

d=1nd×f(nd,md)

发现最外层仍满足整除分块的性质,

于是嵌套两个整除分块即可,时间复杂度 O(n)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int N = 1e7 + 5;
const ll mod = 20101009;

int n, m;
ll prime[N], vis[N], mob[N]; int cnt;
ll sum[N], a[N];

ll calc(int n, int m) {
    ll ans = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        ans = (ans + ((sum[r] - sum[l - 1] + mod) % mod) % mod * a[n / l] % mod * a[m / l] % mod) % mod;
    }
    return ans;
}

int main() {
    sum[1] = mob[1] = 1;
    for (int i = 2; i <= 1e7; i++) {    
        if (!vis[i]) {
            vis[i] = i;
            prime[++cnt] = i;
            mob[i] = -1;
        }
        for (int j = 1; j <= cnt && prime[j] <= 1e7 / i; j++) {
            vis[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) {
                mob[prime[j] * i] = 0;
                break;
            } else {
                mob[prime[j] * i] = -mob[i];
            }
        }
        sum[i] = (sum[i - 1] + mob[i] * i % mod * i % mod) % mod;
    }

    for (int i = 1; i <= 1e7; i++) {
        a[i] = (a[i - 1] + i) % mod;
    }

    cin >> n >> m;
    if (n > m) swap(n, m);
    ll ans = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        ans = (ans + (a[r] - a[l - 1] + mod) % mod * calc(n / l, m / l) % mod) % mod;
    }
    cout << ans << '\n';
    return 0;
}

本文作者:chenwenmo

本文链接:https://www.cnblogs.com/chenwenmo/p/18593492

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   chenwenmo  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起