莫比乌斯反演学习笔记

前置知识

狄利克雷卷积和积性函数

数论分块学习笔记

和式简单推倒技巧


目录#

一、莫比乌斯函数#

  • 定义
  • 性质
    • 证明

二、莫比乌斯反演#

  • 反演公式
    • 形式 1
    • 证明
    • 形式 2

三、例题#

  1. POI2007 zap-Queries
  2. YY 的 GCD
  3. SDOI2014 数表
  4. BZOJ3309 DZY Loves Math
  5. SDOI2015 约数个数和
  6. SDOI2017 数字表格
  7. BZOJ4407 于神之怒加强版
  8. 国家集训队 Crash 的数字表格、JZPTAB

莫比乌斯函数#

定义#

μ(n)={1n=10n 含有平方因子(1)kk 为 n 的本质不同质因子个数

n=i=1kpici,其中 pin 的质因子,ci1

性质#

莫比乌斯函数是积性函数,有以下性质

dnμ(d)=[n=1]

常用于莫比乌斯反演中。

证明#

  1. n=1 时显然成立;
  2. n1 时,设

n=i=1kpici

那么有

dnμ(d)=i=0n(ki)(1)i

考虑我们选 i 个本质不同的质因子,有 (ki) 种方法,其中每个数的莫比乌斯函数值为 (1)i,所以这样计算出原式的贡献。

那么我们需要证明

i=0n(ki)(1)i=0

考虑二项式定理

i=0n(ki)(1)i=i=0n(ni)(1)i1ni=(1+1)n=0

证毕。

莫比乌斯反演#

反演公式#

f(n),g(n) 是定义在正整数集上的两个函数。

形式 1#

若有

f(n)=dng(d)=dng(nd)

则有

g(n)=dnf(nd)μ(d)=dnf(d)μ(nd)

证明#

考虑对下式进行变换

dnμ(d)f(nd)

代入定义,有

dnμ(d)f(nd)=dnμ(d)tndg(t)

可以交换求和号

tng(t)dntμ(d)

详细解释 对于上面这一步变换,有以下考虑。

由于 dn,存在 t 满足 tnd。那么对于每一个符合条件的 t 都有与之对应的 d,那么就可以转化为

dtnμ(d)g(t)

考虑原式,相当于先枚举符合条件的 d,再枚举在此时 d 基础上符合条件的 t,我们发现,换成现在的式子后,对答案的统计仍然不重不漏。

还可以换一种方式考虑,对于原式,我们可以转化为

dμ(d)[dn]tg(t)[dtn]

显然,对于两个限制条件,我们可以直接消去第一个条件,再将艾弗森括号转换进和式,得到

dtnμ(d)g(t)

得到这个式子,我们就可以直接交换 μ(d)g(t) 的位置了,并可以将和式中的一个条件拆开成为两个和式

tng(t)dntμ(d)

又由 dnμ(d)=[n=1] 可以得到

=tng(t)[nt=1]

把艾弗森括号里的部分转化一下

=tng(t)[n=t]

只有当 n=t 时,该式子才有值,即

=g(n)

证毕。

形式 2#

若有

f(n)=ndg(d)=ndg(nd)

则有

g(n)=ndμ(dn)f(d)=ndμ(d)f(dn)

证明与形式 1 的证明类似,不再展开说了。

例题#

POI2007 ZAP-Queries#

题目大意#

给定 nm,求

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

证明#

为了方便处理,我们钦定 nm

首先考虑 d 同时是 ij 的最大公约数,所以满足 d | id | j

于是我们可以把 d 提出来。

i=1 nd j=1 md [gcd(i,j)=1]

然后我们可以利用莫比乌斯函数的结论,变为

i=1 nd j=1 md t|gcd(i,j)μ(t)

此时,实际上 t 是随意枚举的,只要保证 t |gcd(i,j) 就可以。既然 t 不受到前面两个和式的限制,就可以向前提,先枚举 t,又考虑到满足 t |gcd(i,j) 的最大的值也就是 min( nd , md ),所以 t 就枚举到这就行。

又考虑对 μ(t) 产生限制的只有 t,那么就可以把她也向前提前面去,放在 t 的和式后面。我们也可以顺便把其他和式的限制条件放在她后面。

t=1 nd μ(t)i=1nd [t | i]j=1 md [t | j]

然后我们可以发现,观察后面两个和式。例如第二个和式,满足 [t | i]i 最多有  ndt  个。

那么我们这个式子又可以改变,变成下面这个样子。

t=1 nd μ(t) ndt  mdt 

对于后面两个向下取整的部分,我们可以整除分块求,前面的部分我们可以预处理求出莫比乌斯函数。

Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>

using namespace std;

#define int long long

const int N = 50050;

int T,n,m,d;

int pri[N],p[N],cnt;
int miu[N],mu[N];

void Ready() {
    p[1] = 1;
    mu[1] = 1;

    for(int i = 2;i < N; i++) {
        if(p[i] == 0) {
            pri[++cnt] = i;
            mu[i] = -1;
        }

        for(int j = 1;j <= cnt && i * pri[j] < N; j++) {
            p[i * pri[j]] = 1;

            if(i % pri[j] == 0)
                break;
            
            mu[i * pri[j]] = -mu[i];
        }
    }

    for(int i = 1;i < N; i++) 
        miu[i] = mu[i] + miu[i - 1];
    return ;
}

int ans = 0;

signed main() {
    cin >> T;

    Ready();

    while(T--) {
        cin >> n >> m >> d;
        if(n > m)
            swap(n,m);
        n /= d;
        m /= d;
        int l = 1,r = 0;
        while(l <= n) {
            r = min(n / (n / l),m / (m / l));
            ans += (n / l) * (m / l) * (miu[r] - miu[l - 1]);
            l = r + 1;
        }

        cout << ans << "\n";
        ans = 0;
    }
    return 0;
}

YY 的 GCD#

题目大意#

给定 nm,求

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

即求最大公约数为质数的数对个数。

思路#

为了方便处理,我们还是钦定 nm

我们可以枚举一个 d,把原式转化为

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

利用莫比乌斯函数性质,有

d=1n[dP]i=1ndj=1mdtgcd(i,j)μ(t)

交换求和号,令 i=it,j=jt,有

=d=1n[dP]t=1ndμ(t)i=1nd[it]j=1md[jt]=d=1n[dP]t=1ndμ(t)i=1nd[it]j=1md[jt]=d=1n[dP]t=1ndμ(t)ndtmdt

式子推到这里,如果我们去枚举 d,肯定会 TLE,我们令 T=dt,有

T=1nnTmTdT[dP]μ(Td)

我们发现,后面那部分可以预处理。

考虑每个质数 d,对于它的倍数 T,其值加上 μ(Td)

Code
#include <bits/stdc++.h>

using namespace std;

const int N = 10050000;

int T,n,m;

bool p[N];

int mu[N],sum[N],f[N];

int prime[N],cnt;

void Seive() {
    mu[1] = 1;

    for(int i = 2;i <= 10000000; i++) {
        if(!p[i]) {
            cnt ++;
            prime[cnt] = i;
            mu[i] = -1;
        }

        for(int j = 1;i * prime[j] <= 10000000 && j <= cnt; j++) {
            p[i * prime[j]] = 1;

            if(i % prime[j] == 0)
                break;
            
            mu[i * prime[j]] = - mu[i];
        }
    }
    
    for(int i = 1;i <= cnt; i++) 
        for(int j = 1;prime[i] * j <= 10000000; j++) 
            f[prime[i] * j] += mu[j];

    for(int i = 1;i <= 10000000; i++)
        sum[i] = sum[i - 1] + f[i];
}

long long H(int a,int b) {
    long long res = 0;

    if(a == 0)
        return 0;

    int l = 1,r = 0;
    while(l <= n) {
        r = min(a / (a / l),b / (b / l));
        res += (sum[r] - sum[l - 1]) * (long long)(a / l) * (long long)(b / l);
        l = r + 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(false);

    cin >> T;

    Seive();
    
    while(T --> 0) {
        cin >> n >> m;

        if(n > m)
            swap(n,m);

        cout << H(n,m) << "\n";
    }
    return 0;
}

SDOI2014 数表#

题目大意#

有一张 n×m 的数表,其第 i 行第 j 列(1in1jm)的数值为能同时整除 ij 的所有自然数之和。给定 a,计算数表中不大于 a 的数之和。

T2×104,1n,m105,a109)。

思路#

形式化题意:

给定 n,m,a,求

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


为了方便处理,我们还是钦定 nm

先不考虑 a 的限制,那么我们要求的就是

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

可以转化为

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

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

利用常规的变换技巧

d=1nσ(d)i=1ndj=1mdxgcd(i,j)μ(x)

换成整除分块形式

d=1nσ(d)x=1ndμ(x)ndxmdx

和上一道题类似,我们令 T=dx,然后更改枚举顺序,有

T=1nnTmTdTσ(d)μ(Td)

这道题没有 a 的限制的部分就做完了,但还要考虑我们 109a

我们设 g(T)=dTσ(d)μ(Td),我们发现,只有在 σ(d)a 时,才会对 g(T) 产生贡献。

那我们可以离线,把询问按照 a 从小到大排序,在从小到大逐个枚举询问的时候,a 不断变大,使得一些 σ(d)g(T) 产生贡献。

我们枚举倍数来找到所有的 T,需要动态修改 g(T) 且区间查询,考虑树状数组。

Code
#include <bits/stdc++.h>

using namespace std;

const int M = 1e5;
const int N = 100500;
const long long Mod = 1 << 31;

int T;

bool p[N];
int prime[N],cnt;
int num[N];
int sigma[N],mu[N];

unsigned long long ans[N];

struct Query{
    int id;
    int n,m,a;

    bool operator < (const Query &t) const {
        return a < t.a;
    }
}q[N];

struct Num{
    int x;

    bool operator < (const Num &t) const {
        return sigma[x] < sigma[t.x];
    }
}a[N];

void Seive() {
    sigma[1] = mu[1] = 1;
    for(int i = 2;i <= M; i++) {
        if(!p[i]) {
            cnt ++;
            prime[cnt] = i;
            mu[i] = -1;
            sigma[i] = num[i] = i + 1;
        }

        for(int j = 1;j <= cnt && i * prime[j] <= M; j++) {
            p[i * prime[j]] = 1;

            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                num[i * prime[j]] = num[i] * prime[j] + 1;
                sigma[i * prime[j]] = sigma[i] / num[i] * num[i * prime[j]];

                break;
            }

            mu[i * prime[j]] = - mu[i];
            num[i * prime[j]] = prime[j] + 1;
            sigma[i * prime[j]] = sigma[i] * (prime[j] + 1);
        }
    }

    for(int i = 1;i <= M; i++)
        a[i].x = i;

    sort(a + 1,a + M + 1);
    return ;
}

int bit[N];

class BIT{
private:
    int lowbit(int x) {
        return x & (-x);
    }

public:
    void Add(int x,int y) {
        for(int i = x;i <= M; i += lowbit(i))
            bit[i] = (1ll * bit[i] + y) % Mod;
        
        return ;
    }

    int Query(int x) {
        int ans = 0;
        for(int i = x;i >= 1; i -= lowbit(i))
            ans = (1ll * ans + bit[i]) % Mod;
        
        return ans;
    }
}tree;

void Insert(int x) {
    for(int k = 1;x * k <= M; k++)
        tree.Add(x * k,1ll * mu[k] * sigma[x] % Mod);
        // 预处理
}

int query(int n,int m) {
    long long res = 0,last = 0,cur;

    for(int l = 1,r = 0;l <= n;l = r + 1,last = cur) {
        
        r = min(n / (n / l),m / (m / l));
        cur = tree.Query(r);
        res = (res + 1ll * (1ll * cur - last) * (n / l) % Mod * (m / l) % Mod) % Mod;
    }
    return res % Mod;
}

signed main() {
    ios::sync_with_stdio(false);
    cin >> T;

    Seive();
            
    for(int i = 1;i <= T; i++) {
        cin >> q[i].n >> q[i].m >> q[i].a;
        q[i].id = i;

        if(q[i].n > q[i].m)
            swap(q[i].n,q[i].m);
    }

    sort(q + 1,q + T + 1);

    int j = 1;

    for(int i = 1;i <= T; i++) {
        for(    ;j <= M && sigma[a[j].x] <= q[i].a; j++) {
            Insert(a[j].x);
        }
        ans[q[i].id] = query(q[i].n,q[i].m);
    }

    for(int i = 1;i <= T; i++) 
        cout << (ans[i] % Mod + Mod) % Mod << "\n";
    return 0;
}

BZOJ3309 DZY Loves Math#

题目大意#

对于正整数 n,定义 f(n)n 所包含质因子的最大幂指数。例如 f(1960)=f(23×51×72)=3f(10007)=1f(1)=0

给定正整数 a,b,求下式的值:

i=1aj=1bf(gcd(i,j))

推导#

首先记 n=min(a,b),m=max(a,b)

      i=1aj=1bf(gcd(i,j))=d=1nf(d)i=1nj=1m[gcd(i,j)=d]=d=1nf(d)i=1ndj=1md[gcd(i,j)=1]=d=1nf(d)i=1ndj=1mdtgcd(i,j)μ(t)=d=1nf(d)i=1ndj=1mdtitjμ(t)=d=1nf(d)t=1ndμ(t)i=1nd[ti]j=1md[tj]=d=1nf(d)t=1ndμ(t)ndtmdt

T=dt(十分常用的技巧),那么有

=T=1nnTmTdTμ(Td)f(d)

h=fμ,那么有

=T=1nnTmTh(T)


那么,现在的问题是如何求 h?考虑求 h(n)

我们可以把 n 进行质因数分解:

n=i=1mpici

考虑 h(T) 的原本写法:

h(T)=dTμ(Td)f(d)

T=i=1mpiaid=i=1mpibi

考虑 μ(n) 的定义,当 n 中含有平方质因子时 μ(n)=0,即不会在 h 中产生贡献,可以不考虑。

所以 Td 的质因数要满足最大幂指数小于 2,即 aibi{0,1}

所以将 h(T) 改写为如下形式时

h(T)=ab=Tf(a)μ(b)

b=i=1mpidi(di{0,1})

l=maxi=1mci,k=i=1m[ci=l]。即 ln 所包含质因子的最大幂指数,kn 所包含质因子幂指数中最大幂指数的个数。

我们发现 f(a) 的取值只有 ll1 两种可能(b 可能把最大幂指数的都取走,导致 f(a) 的取值少了 1)。

我们先按 k=mkm 两种情况分类讨论,在每一项讨论中,我们再分 f(a)=lf(a)=l1 两种子情况。


k=m 时,贡献为(加号前为 l 的情况,加号后为 l1 的情况)

s=0m1(l)×(1)s×(ms)+(l1)×(1)m×(mm)

f(a)=l 的情况不会把最大幂指数的质因数都取走。

所以我们枚举到 m1,中间的 (1)s(1)m 实质上就是莫比乌斯函数,最右边的二项式系数是枚举选取的方案。

将上面的式子加号右边的部分拆开,把带 l 的部分合并到左面,得到

s=0m(l)×(1)s×(ms)+1×(1)m

二项式定理,得

1×(1)m=(1)m+1


km 时,

f(a)=l 时,设在 kci=l 的质数中选了 t 个,另外 mk 个质数中选了 s 个,贡献为:

s=0mkt=0k1(kt)×l×(1)s+t×(mks)

中间的 (1)s+t 仍是莫比乌斯函数。因为 f(a)=l,所以 k 个质数不能被选完,因此枚举到 k1

左右拆开,分别二项式定理,得

t=0k1(kt)×l×(1)t×s=0mk(1)s×1mks×(mks)=0

f(a)=l1 时,kci=l 的质数一定全部被选择,设在另外 mk 个质数中选择 s 个,贡献为:

     s=0mk(l1)×(1)k×(1)s×(mks)=s=0mk(l1)×(1)k×(1)s×1mks×(mks)=(l1)×(1)k×s=0mk(1)s×1mks×(mks)=0


所以最终 h(n) 的表达式为

h(n)={(1)m+1k=m0km

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 1e7;
const int M = 10500;

int T,n,m;

bool p[N + M];
int pri[N >> 2],cnt,low[N + M];
int h[N + M],a[N + M];

void Seive() {
    p[1] = 1;

    for(int i = 2;i <= N; i++) {
        if(!p[i]) {
            cnt ++;
            low[i] = pri[cnt] = i;
            a[i] = h[i] = 1;
        }

        for(int j = 1;j <= cnt && i * pri[j] <= N; j++) {
            p[i * pri[j]] = 1;

            if(i % pri[j] == 0) {
                a[i * pri[j]] = a[i] + 1;
                low[i * pri[j]] = low[i] * pri[j];

                if(i == low[i]) 
                    h[i * pri[j]] = 1;
                else if(a[i / low[i]] == a[i * pri[j]])
                    h[i * pri[j]] = -h[i / low[i]];
                else
                    h[i * pri[j]] = 0;

                break;
            }

            a[i * pri[j]] = 1;
            low[i * pri[j]] = pri[j];
            if(a[i] == 1)
                h[i * pri[j]] = -h[i];
            else
                h[i * pri[j]] = 0;
        }
    }

    for(int i = 1;i <= N; i++)
        h[i] += h[i - 1];
    
    return ;
}

signed main() {
    ios::sync_with_stdio(false);
    Seive();
    
    cin >> T;

    while(T --> 0) {
        cin >> n >> m;

        if(n > m)
            swap(n,m);

        int l = 1,r = 0,ans = 0;

        while(l <= n) {
            r = min(n / (n / l),m / (m / l));
            ans += (n / l) * (m / l) * (h[r] - h[l - 1]);
            l = r + 1;
        }

        cout << ans << "\n";
    }
    return 0;
}

SDOI2015 约数个数和#

题目大意#

d(x)x 的约数个数(即 σ0(x)),给定 n,m,求

i=1nj=1md(ij)

推导#

先给出一个 d(x) 的性质

d(ij)=xiyj[gcd(x,y)=1]

证明i 中有因子 paj 中有因子 pbij 的因子 k 中有因子 pc

显然 ij 的每个因数对答案的贡献都是 1,我们考虑构建一种策略使得 ij 的因数与数对 (x,y) 构成双射。

设数对为 (x,y)

  • 如果 ca,我们只在 i 中选择,即 x=c,y=0
  • 否则,我们在 i 中选择 pa,在 j 中选择 pca(表示为 pa+y)。

c>a 时,我们把取满的部分赋为 0,即 x=0,y=ca

显然,x,y 中有且仅有一个数为 0

那么枚举 ij 的约数,就转化为了枚举我们选择策略的数对 (x,y)

gcd(px,py)=pmin(x,y)=p0=1

所以

gcd(x,y)=1x=0y=0

即满足我们的选择策略。

这样就不重不漏地枚举了 ij 的每一个约数,故等式成立。


那么利用上面的式子转化式子。

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

x,y 提前,

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

x,y 换成 i,j

i=1nj=1mnimjdgcd(i,j)μ(d)

d 提前,

d=1nμ(d)i=1ndj=1mdndimdj

设函数 g(n)=i=1nni,那么有

d=1nμ(d)g(nd)g(md)

显然,g(n)=i=1nd(i)

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int M = 50050;

int T,n,m;

int d[100500],num[100500],mu[100500],g[100500];
int pri[100500],cnt = 0;
bool p[100500];

void Seive() {
    d[1] = 1;
    mu[1] = 1;

    for(int i = 2;i <= M; i++) {
        if(!p[i]) {
            cnt ++;
            pri[cnt] = i;
            d[i] = 2;
            num[i] = 1;
            mu[i] = -1;
        }

        for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
            p[i * pri[j]] = 1;

            if(i % pri[j] == 0) {
                num[i * pri[j]] = num[i] + 1;
                d[i * pri[j]] = d[i] / num[i * pri[j]] * (num[i * pri[j]] + 1);
                break;
            }

            num[i * pri[j]] = 1;
            d[i * pri[j]] = d[i] * 2;
            mu[i * pri[j]] = -mu[i];
        }
    }

    for(int i = 1;i <= M; i++) {
        g[i] = g[i - 1] + d[i];
        mu[i] += mu[i - 1];
    }
    return ;
}

signed main() {
    ios::sync_with_stdio(false);

    Seive();

    cin >> T;
    while(T --> 0) {
        cin >> n >> m;
        
        if(n > m)
            swap(n,m);

        int ans = 0;
        for(int l = 1,r = 0;l <= n; l = r + 1) {
            r = min(n / (n / l),m / (m / l));
            ans += (mu[r] - mu[l - 1]) * g[n / l] * g[m / l];
        }

        cout << ans << "\n";
    }
    return 0;
}

SDOI2017 数字表格#

题目大意#

f(i) 表示斐波那契数列的第 i 项,求

i=1nj=1mf(gcd(i,j))

答案对 109+7 取模。

推导#

i=1nj=1mf(gcd(i,j))

首先是经典枚举技巧,

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

指数部分单独取出来处理,直接跳步,得到

t=1ndμ(t)ndtmdt

代回原式,

d=1nf(d)t=1ndμ(t)ndtmdt

老套路,设 T=dt

T=1ndTf(T)μ(Td)nTmT

把中间一部分提出来单独处理,

g(n)=dnf(d)μ(nd)

代入原式,就得到了

T=1ng(T)nTmT

g(T) 直接暴力求。

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long

int T,n,m;

const int N = 1005000;
const int M = 1000000;
const int Mod = 1e9 + 7;

bool p[N];
int pri[N],cnt;
int mu[N],inv[N];
int f[N],g[N];

long long Pow(long long a ,long long b) {
    long long ans = 1 ;
    long long base = a % Mod;

    while( b > 0 ) { 
   	    if( b&1 != 0 )
   		    ans = ans * base % Mod;
   	    base = base * base % Mod;
   	    b = b >> 1;
    }
   return ans;
}

void Seive() {
    mu[1] = p[1] = f[1] = inv[1] = 1;

    for(int i = 2;i <= M; i++) {
        f[i] = (f[i - 1] + f[i - 2]) % Mod;
        inv[i] = Pow(f[i],Mod - 2) % Mod;

        if(!p[i]) {
            mu[i] = -1;
            cnt ++;
            pri[cnt] = i;
        }

        for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
            p[i * pri[j]] = 1;
            if(i % pri[j] == 0) {
                mu[i * pri[j]] = 0;
                break;
            }

            mu[i * pri[j]] = -mu[i];
        }
    }

    for(int i = 0;i <= M; i++)
        g[i] = 1;
    
    for(int i = 1;i <= M; i++) {
        if(mu[i] == 0)
            continue;
        
        for(int j = i;j <= M; j += i) {
            if(mu[i] == 1)
                g[j] = g[j] * f[j / i] % Mod;
            else
                g[j] = g[j] * inv[j / i] % Mod;
        }
    }

    for(int i = 2;i <= M; i++)
        g[i] = g[i] * g[i - 1] % Mod;
    return ;
}

signed main() {
    ios::sync_with_stdio(false);

    Seive();
    
    cin >> T;

    while(T --> 0) {
        cin >> n >> m;
        
        if(n > m)
            swap(n,m);

        int ans = 1,Inv = 0;

        for(int l = 1,r = 0;l <= n; l = r + 1) {
            // cerr << "a";
            r = min(n / (n / l),m / (m / l));
            Inv = g[r] * Pow(g[l - 1],Mod - 2) % Mod;
            ans = ans * Pow(Inv,(n / l) * (m / l) % (Mod - 1)) % Mod;
        }

        ans = (ans + Mod) % Mod;

        cout << ans << "\n";
    }
    return 0;
}

BZOJ4407 于神之怒加强版#

题目大意#

给定 n,m,k,计算

i=1nj=1mgcd(i,j)k

109+7 取模的结果。

推导#

经典枚举

     d=1ndki=1nj=1m[gcd(i,j)=d]=d=1ndki=1ndj=1mdtgcd(i,j)μ(t)=d=1ndkndtmdtt=1ndμ(t)=T=1ndTdkμ(Td)nTmT

g(n)=dndkμ(nd),即 g(n)=idkμ,显然这是一个积性函数。

p 为质数时,d{1,p},容易得到 g(p)=pk1

考虑 p 为质数时,g(pc) 的值:

     dpcdkμ(pcd)=i=0c(pi)kμ(pcpi)=i=0c(pi)kμ(pci)

ci>1 时,有 μ(pci)=0,不产生贡献。产生贡献的只有 i=ci=c1 两种情况。

容易得到 g(pc)=pkcpkck

考虑 gcd(p,y)=1g(pcy) 的值,根据积性函数的性质即可。

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long

int T,k,n,m;

const int N = 5005000;
const int M = 5000000;
const int Mod = 1e9 + 7;

bool p[N];
int pri[N],cnt;
int g[N],sum[N];

long long Pow(long long a ,long long b) {
    long long ans = 1 ;
    long long base = a % Mod;
    b = b % (Mod - 1);

    while(b) { 
   	    if(b & 1)
   		    ans = ans * base % Mod;
   	    base = base * base % Mod;
   	    b >>= 1;
    }

   return ans;
}

void Seive() {
    memset(g,1,sizeof(g));
    g[1] = 1;

    for(int i = 2;i <= M; i++) {
        if(!p[i]) {
            cnt ++;
            pri[cnt] = i;
            g[i] = (Pow(i,k) - 1 + Mod) % Mod;
        }

        for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
            p[i * pri[j]] = 1;

            if(i % pri[j] == 0) {
                g[i * pri[j]] = g[i] * (g[pri[j]] + 1) % Mod;
                break;
            }

            g[i * pri[j]] = g[i] * g[pri[j]] % Mod;
        }
    }

    for(int i = 1;i <= M; i++)
        sum[i] = (sum[i - 1] + g[i]) % Mod;
    return ;
}

signed main() {
    ios::sync_with_stdio(false);
    
    cin >> T >> k;
    
    Seive();

    while(T --> 0) {
        cin >> n >> m;
        
        if(n > m)
            swap(n,m);

        int ans = 0;

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

        ans = (ans + Mod) % Mod;

        cout << ans << "\n";
    }
    return 0;
}

Crash 的数字表格 / JZPTAB#

题目大意#

给定 n,m,求

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

答案对 20101009 取模。

推导#

i=1nj=1mi×jgcd(i,j)

经典枚举

d=1ni=1nj=1mi×jd[gcd(i,j)=d]

提出 di,j 都除以了 d,多除的一个放到前面去,

d=1ndi=1ndj=1mdi×j[gcd(i,j)=1]

然后得

d=1ndi=1ndj=1mdi×jtgcd(i,j)μ(t)

g(n)=n×(n+1)2

d=1ndt=1ndt2μ(t)×g(ndt)×g(mdt)

T=dt,有

T=1ng(ndt)×g(mdt)×T×dTdμ(d)

f(n)=dndμ(d),显然是积性函数。

p 为质数时,有 f(p)=1p,f(pk)=f(p)

Code
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 10005000;
const int M = 10000000;
const int Mod = 20101009;

int n,m;

bool p[N];
int cnt,pri[N >> 4];
int f[N];

void Seive() {
    f[1] = 1;

    for(int i = 2;i <= M; i++) {
        if(!p[i]) {
            cnt ++;
            pri[cnt] = i;
            f[i] = (1 - i) % Mod;
        }

        for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
            p[i * pri[j]] = 1;

            if(i % pri[j] == 0) {
                f[i * pri[j]] = f[i];
                break;
            }

            f[i * pri[j]] = f[i] * f[pri[j]] % Mod;
        }
    }

    for(int i = 2;i <= M; i++) 
        f[i] = (f[i] * i % Mod + f[i - 1]) % Mod;
    return ;
}

int g(int x) {
    return (x * (x + 1) / 2) % Mod;
}

signed main() {
    ios::sync_with_stdio(false);
    
    Seive();

    cin >> n >> m;

    if(n > m)
        swap(n,m);
    
    int ans = 0;
    
    for(int l = 1,r = 0;l <= n; l = r + 1) {
        r = min(n / (n / l),m / (m / l));
        ans = (ans + g(n / l) * g(m / l) % Mod * (f[r] - f[l - 1] + Mod) % Mod + Mod) % Mod;
    }

    cout << ans << "\n";
    return 0;
}

END

作者:白简

出处:https://www.cnblogs.com/baijian0212/p/Mobius-Reverse.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   -白简-  阅读(105)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu