欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

莫比乌斯反演小记

程序员文章站 2023-12-23 21:45:15
写在前面 这是蒟蒻第一次写这么长的博文 $gyh\ nb$,$\text{OI Wiki}\ nb$ 如果觉得写得凑合就点个支持吧$qwq$ 前置知识 "积性函数" 、 "狄利克雷卷积" 、 "数论分块(这一篇去找gyh吧我讲也讲不好)" ~~(有空慢慢补)~~ Mobius函数 定义 莫比乌斯函数 ......

写在前面

这是蒟蒻第一次写这么长的博文

\(gyh\ nb\)\(\text{oi-wiki}\ nb\)

如果觉得写得凑合就点个支持吧\(qwq\)

前置知识

、、(有空慢慢补)

mobius函数

定义

莫比乌斯函数\(\mu(n)\)定义为:

\[\mu(n)=\left\{ \begin{aligned} 1, & n=1 \\ (-1)^{s}, & n=p_1p_2…p_s(s为n的本质不同的质因子个数)\\0,&n有平方因子\end{aligned} \right.\]

其中\(p_1p_2…,p_s\)是不同素数。

可以看出,当\(n\)没有平方因子时,\(\mu(n)\)非零。

\(\mu\)也是积性函数。

性质

莫比乌斯函数具有如下的性质:

\[\sum_{d|n}\mu(d)=\epsilon(n)=[n=1] \]

使用狄利克雷卷积来表示,即

\[\mu*1=\epsilon \]

证明:

\(n=1\)时显然成立。

\(n>1\),设\(n\)\(s\)个不同的素因子,由于\(\mu(d)\neq0\)当且仅当\(d\)无平方因子,故\(d\)中每个素因子的指数只能为\(0\)\(1\)

\(n\)的某个因子\(d\),有\(\mu(d)=(-1)^i\),则它由\(i\)个本质不同的质因子构成。因为质因子的总数为\(s\),所以满足上式的因子数有\(c_s^i\)个。

所以我们就可以对于原式,转化为枚举\(\mu(d)\)的值,同时运用二项式定理,故有

\[\sum_{d|n}\mu(d)=\sum_{i=0}^{s}c_s^i\times(-1)^i=\sum_{i=0}^{s}c_s^i\times(-1)^i\times 1^{s-i}=(1+(-1))^s \]

该式在\(s=0\)\(n=1\)时为1,否则为\(0\)

求莫比乌斯函数

因为莫比乌斯函数是积性函数,所以可以用线性筛

int n, cnt, p[a], mu[a];
bool vis[a];

void getmu() {
    mu[1] = 1;
    for (int i = 2; i <= n; i++) {
	if (!vis[i]) mu[i] = -1, p[++cnt] = i;
	for (int j = 1; j <= cnt && i * p[j] <= n; j++) {
	    vis[i * p[j]] = 1;
	    if (i % p[j] == 0) { mu[i * p[j]] = 0; break; }
	    mu[i * p[j]] -= mu[i];
	}
    }
}

莫比乌斯反演公式

\(f(n)\)\(g(n)\)为两个数论函数。

如果有

\[f(n)=\sum\limits_{d|n}g(d) \]

则有

\[g(n)=\sum\limits_{d|n}\mu(d)f(\frac{n}{d}) \]

证明

  • 法一:对原式做数论变换

    1. \(\sum\limits_{d|n}g(d)\)替换\(f(n)\),即

      \[\sum\limits_{d|n}\mu(d)f(\frac{n}{d})=\sum\limits_{d|n}\mu(d)\sum\limits_{k|\frac nd}g(k) \]
    2. 变换求和顺序得

      \[\sum\limits_{k|n}g(k)\sum\limits_{d|\frac n k}\mu(\frac nk) \]
    3. 因为\(\sum\limits_{d|n}\mu(d)=[n=1]\),所以只有在\(\frac{n}{k}=1\)\(n=k\)时才会成立,所以上式等价于

      \[\sum\limits_{d|n}[n=k]g(k)=g(n) \]

    得证

  • 法二:利用卷积

    原问题为:已知\(f=g*1\),证明\(g=f*\mu\)

    转化:\(f*\mu=g*1*\mu=g*\varepsilon=g\)\(1*\mu=\varepsilon\)

    再次得证= =

小性质

\([\gcd(i,j)=1]\leftrightarrow\sum\limits_{d|\gcd(i,j)}\mu(d)\)

证明

  • 法一:

    \(n=\gcd(i,j)\),那么等式右边\(=\sum\limits_{d|n}\mu(d)=[n=1]=[\gcd(i,j)=1]=\)等式左边

  • 法二:

    利用单位函数暴力拆开:\([\gcd(i,j)=1]=\varepsilon(\gcd(i,j))=\sum\limits_{d|\gcd(i,j)}\mu(d)\)

做题思路&&应用

利用狄利克雷卷积可以解决一系列求和问题。常见做法是使用一个狄利克雷卷积替换求和式中的一部分,然后调换求和顺序,最终降低时间复杂度。

经常利用的卷积有\(\mu*1=\epsilon\)\(\text{id}=\varphi*1\)

还是以题为主吧,但是做的题也会单独写题解,毕竟要多水几篇博客的嘛/huaji

洛谷 p2522 [haoi2011]problem b

题目链接

\(n\)组询问,每次给出\(a,b,c,d,k\),求\(\sum\limits_{x=a}^{b}\sum\limits_{y=c}^{d}[\gcd(x,y)=k]\)

\(f(n,m)=\sum\limits_{i=1}^{n}\sum\limits_{j= 1}^{m}[\gcd(i,j)=k]\)

那么根据容斥原理,题目中的式子就转化成了\(f(b,d)-f(b, c - 1) - f(a - 1,d) + f(a - 1, c - 1)\)

所以我们接下来的问题就转化为了如何求\(f\)的值

现在来化简\(f\)的值

  1. 容易得出原式等价于$$\sum\limits_{i = 1}^{\lfloor\frac{n}{k}\rfloor}\sum\limits_{j = 1}^{\lfloor\frac{m}{k}\rfloor}[\gcd(i,j) = 1]$$

  2. 因为\(\epsilon(n) =\sum\limits_{d|n}\mu(d)=[n=1]\),由此可将原式化为

    \[\sum\limits_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{k}\rfloor}\sum\limits_{d|gcd(i,j)}\mu(d) \]
  3. 改变枚举对象并改变枚举顺序,先枚举\(d\),得

    \[\sum\limits_{d=1}^{\min(n,m)}\mu(d)\sum\limits_{i=1}^{\lfloor\frac{n}{k}\rfloor}[d|i]\sum\limits_{j=1}^{\lfloor\frac{m}{k}\rfloor}[d|j] \]

    也就是说当\(d|i\)\(d|j\)时,\(d|\gcd(i,j)\)

  4. 易得\(1\sim \lfloor\frac{n}{k}\rfloor\)中一共有\(\lfloor\frac{n}{dk}\rfloor\)\(d\)的倍数,同理\(1\sim \lfloor\frac{m}{k}\rfloor\)中一共有\(\lfloor\frac{m}{dk}\rfloor\)\(d\)的倍数,于是原式化为$$\sum\limits_{d=1}^{\min(n,m)}\mu(d)\lfloor\frac{n}{dk}\rfloor\lfloor\frac{m}{dk}\rfloor$$

此时已经可以\(o(n)\)求解,但是过不了,因为有很多值相同的区间,所以可以用数论分块来做

先预处理\(\mu\),再用数论分块,复杂度\(o(n+t\sqrt n)\)

我的代码每次得分玄学,看评测机心情,建议自己写

/*
author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int a = 1e6 + 11;
const int b = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, a, b, c, d, k, cnt, p[a], mu[a], sum[a];
bool vis[a];

void getmu() {
	int max = 50010;
	mu[1] = 1;
	for (int i = 2; i <= max; i++) {
		if (!vis[i]) mu[i] = -1, p[++cnt] = i;
		for (int j = 1; j <= cnt && i * p[j] <= max; j++) {
			vis[i * p[j]] = true;
			if (i % p[j] == 0) break;
			mu[i * p[j]] -= mu[i];
		}
	}
	for (int i = 1; i <= max; i++) sum[i] = sum[i - 1] + mu[i];
}

int work(int x, int y) {
	int ans = 0ll;
	int max = min(x, y);
	for (int l = 1, r; l <= max; l = r + 1) {
		r = min(x / (x / l), y / (y / l));
		ans += (1ll * x / (l * k)) * (1ll * y / (l * k)) * 1ll * (sum[r] - sum[l - 1]); 
	}
	return ans;
}

void solve() {
	a = read(), b = read(), c = read(), d = read(), k = read();
	cout << work(b, d) - work(a - 1, d) - work(b, c - 1) + work(a - 1, c - 1) << '\n';
}

signed main() {
	getmu();
	int t = read();
	while (t--) solve();
	return 0;
}

洛谷 p3455 [poi2007]zap-queries

题目链接

\(t\)组询问,每次询问求

\[\sum\limits_{x=1}^{a}\sum\limits_{y=1}^{b}[\gcd(x,y)=d] \]

因为我不喜欢用\(x、y、a、b、d\),所以一一对应换成\(i、j、n、m、k\)

直接淦式子

\[\begin{align*}&\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[\gcd(i,j)=k]\\=& \sum\limits_{i=1}^{\lfloor \frac nk\rfloor}\sum\limits_{j=1}^{\lfloor\frac mk\rfloor}[\gcd(i,j)=1]\\=&\sum\limits_{i=1}^{\lfloor \frac nk\rfloor}\sum\limits_{j=1}^{\lfloor\frac mk\rfloor}\sum\limits_{d|\gcd(i,j)}\mu(d)\\=&\sum\limits_{i=1}^{\lfloor \frac nk\rfloor}\sum\limits_{j=1}^{\lfloor\frac mk\rfloor}\sum\limits_{d|i}\sum _{d|j}\mu(d)\\=&\sum\limits_{d=1}^{\min(\lfloor\frac nk\rfloor,\lfloor\frac mk\rfloor)}\mu(d)\sum\limits_{i=1}^{\lfloor \frac nk\rfloor}\sum\limits_{j=1}^{\lfloor\frac mk\rfloor}\sum\limits_{d|i}\sum\limits_{d|j}1\\=&\sum\limits_{d=1}^{\min(\lfloor\frac nk\rfloor,\lfloor\frac mk\rfloor)}\mu(d)\sum\limits_{i=1}^{\lfloor \frac nk\rfloor}\sum\limits_{d|i}1\sum\limits_{j=1}^{\lfloor\frac mk\rfloor}\sum\limits_{d|j}1\\=&\sum\limits_{d=1}^{\min(\lfloor\frac nk\rfloor,\lfloor\frac mk\rfloor)}\mu(d)\lfloor\frac{\lfloor \frac nk\rfloor}d\rfloor\lfloor\lfloor\frac{\frac mk\rfloor}d\rfloor\\=&\sum\limits_{d=1}^{\min(\lfloor\frac nk\rfloor,\lfloor\frac mk\rfloor)}\mu(d)\lfloor\frac n{kd}\rfloor\lfloor\frac m{kd}\rfloor\end{align*} \]

现在就可以每次询问\(o(n)\)做这道题了

但是跑不过啊,不过显然可以数论分块,所以我们就可以\(o(\sqrt n)\)回答每次询问了

/*
author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int a = 5e5 + 11;
const int b = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, m, k, mu[a], p[a], sum[a], cnt;
bool vis[a];

void getmu(int n) {
	mu[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (!vis[i]) p[++cnt] = i, mu[i] = -1;
		for (int j = 1; j <= cnt && i * p[j] <= n; j++) {
			vis[i * p[j]] = 1;
			if (i % p[j] == 0) break;
			mu[i * p[j]] -= mu[i];
		}
	}
	for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}

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

int main() {
	getmu(50000);
	int t = read();
	while (t--) {
		n = read(), m = read(), k = read();
		cout << solve(n, m, k) << '\n';
	}
	return 0;
}

洛谷 p1829 [国家集训队]crash的数字表格 / jzptab

题目链接

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\text{lcm}(i,j)(\bmod 20101009) \]

容易想到原式等价于

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\frac{i* j}{\gcd(i,j)} \]

枚举\(i,j\)的最大公约数\(d\),显然\(\gcd(\frac id,\frac jd)=1\),即\(\frac id\)\(\frac jd\)互质

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^m\sum\limits_{d|i,d|j,\gcd(\frac id,\frac jd)=1}\frac{i*j}d \]

变换求和顺序

\[\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}[\gcd(i,j)=1]i*j \]

\(sum(n,m)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[\gcd(i,j)=1]i*j\)

对其进行化简,用\(\varepsilon(\gcd(i,j))\)替换\([\gcd(i,j)=1]\)

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\sum\limits_{d|\gcd(i,j)}\mu(d)*i*j \]

转化为首先枚举约数

\[\sum\limits_{d=1}^{\min(n,m)}\sum\limits_{d|i}^{n}\sum\limits_{d|j}^{m}\mu(d)*i*j \]

\(i=i'*d,j=j'*d\),则可以进一步转化

\[\sum\limits_{d=1}^{\min(n,m)}\mu(d)*d^2*\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}i*j \]

前半段可以处理前缀和,后半段可以\(o(1)\)求,设

\[q(n,m)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}i*j=\frac{n*(n+1)}{2}*\frac{m*(m+1)}{2} \]

显然可以\(o(1)\)求解

到现在

\[sum(n,m)=\sum\limits_{d=1}^{\min(n,m)}\mu(d)*d^2*q(\lfloor\frac nd \rfloor,\lfloor\frac md\rfloor) \]

可以用数论分块求解

回带到原式中

\[\sum\limits_{d=1}^{\min(n, m)}d*sum(\lfloor\frac nd \rfloor,\lfloor\frac md\rfloor) \]

又可以数论分块求解了

然后就做完啦

/*
author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;

const int a = 1e7 + 11;
const int b = 1e6 + 11;
const int mod = 20101009;
const int inf = 0x3f3f3f3f;

inline int read() {
    char c = getchar(); int x = 0, f = 1;
    for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
    for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
    return x * f;
}

bool vis[a];
int n, m, mu[a], p[b], sum[a], cnt;

void getmu() {
    mu[1] = 1;
    int k = min(n, m);
    for (int i = 2; i <= k; i++) {
        if (!vis[i]) p[++cnt] = i, mu[i] = -1;
        for (int j = 1; j <= cnt && i * p[j] <= k; ++j) {
            vis[i * p[j]] = 1;
            if (i % p[j] == 0) break;
            mu[i * p[j]] = -mu[i];
        }
    }
    for (int i = 1; i <= k; i++) sum[i] = (sum[i - 1] + i * i % mod * mu[i]) % mod;
}

int sum(int x, int y) { 
	return (x * (x + 1) / 2 % mod) * (y * (y + 1) / 2 % mod) % mod; 
}

int solve2(int x, int y) {
    int res = 0;
    for (int i = 1, j; i <= min(x, y); i = j + 1) {
        j = min(x / (x / i), y / (y / i));
        res = (res + 1ll * (sum[j] - sum[i - 1] + mod) * sum(x / i, y / i) % mod) % mod;
    }
    return res;
}

int solve(int x, int y) {
    int res = 0;
    for (int i = 1, j; i <= min(x, y); i = j + 1) {
        j = min(x / (x / i), y / (y / i));
        res = (res + 1ll * (j - i + 1) * (i + j) / 2 % mod * solve2(x / i, y / i) % mod) % mod;
    }
    return res;
}

signed main() {
    n = read(), m = read();
    getmu();
    cout << solve(n, m) << '\n';
}

洛谷 p2257 yy的gcd

题目链接

给定\(n,m\),求二元组\((x,y)\)的个数,满足\(1\leq x\leq n,1\leq y\leq m\),且\(gcd(x,y)\)是素数。

\(n,m\leq 10^7\),自带多组数据,至多\(10^{4}\)组数据。

思路与第一题problem b类似,在这里不再赘述,只给出代码= =

#include <cmath> 
#include <cstdio>
#include <cstring> 
#include <iostream>
using namespace std;

const int a = 1e7 + 11; 

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for ( ; !isdigit(c); c = getchar()) if(c == '-') f = -1; 
	for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

bool vis[a];
long long sum[a];
int prim[a], mu[a], g[a], cnt, n, m;

void get_mu(int n) {
    mu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            mu[i] = -1;
            prim[++cnt] = i;
        }
        for (int j = 1; j <= cnt && prim[j] * i <= n; j++) {
            vis[i * prim[j]] = 1;
            if (i % prim[j] == 0) break;
            else mu[prim[j] * i] = - mu[i];
        }
    }
    for (int j = 1; j <= cnt; j++)
        for (int i = 1; i * prim[j] <= n; i++) g[i * prim[j]] += mu[i];
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (long long)g[i];
}

signed main() {
    int t = read();
    get_mu(10000000);
    while (t--) {
        n = read(), m = read();
        if (n > m) swap(n, m);
        long long ans = 0;
        for (int l = 1, r; l <= n; l = r + 1) {
            r = min(n / (n / l), m / (m / l));
            ans += 1ll * (n / l) * (m / l) * (sum[r] - sum[l - 1]);
        }
        cout << ans << '\n';
    }
    return 0;
}

洛谷 p3327 [sdoi2015]约数个数和

题目链接

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}d(ij) \]

\(d(x)\)\(x\)的约数个数和

需要用到

\[d(ij)=\sum\limits_{x|i}\sum\limits_{y|j}[\gcd(x,y)=1] \]

证明我也不会

然后自己推导吧,在此不再赘述

/*
author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int a = 5e5 + 11;
const int b = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

bool vis[a];
int n, m, p[a], mu[a], cnt, sum[a];
long long g[a], ans;

void getmu(int n) {
	mu[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (!vis[i]) mu[i] = -1, p[++cnt] = i;
		for (int j = 1; j <= cnt && i * p[j] <= n; j++) {
			vis[i * p[j]] = 1;
			if (i % p[j] == 0) break;
			mu[i * p[j]] -= mu[i];
		}
	}
	for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
	for (int i = 1; i <= n; i++) {
		int ans = 0;
		for (int l = 1, r; l <= i; l = r + 1) {
			r = (i / (i / l));
			ans += 1ll * (r - l + 1) * (i / l);
		}
		g[i] = ans;
	}
}

signed main() {
	int t = read();
	getmu(50000);
	while (t--) {
		n = read(), m = read();
		int maxn = min(n, m);
		ans = 0;
		for (int l = 1, r; l <= maxn; l = r + 1) {
			r = min(n / (n / l), m / (m / l));
			ans += 1ll * (sum[r] - sum[l - 1]) * 1ll * g[n / l] * 1ll * g[m / l];
		}
		cout << ans << '\n';
	}
	return 0;
}

洛谷 p4449 于神之怒加强版

\[\sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j)^k(\bmod 1e9+7) \]

还是直接淦式子

\[\begin{align*}&\sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j)^k\\=&\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d=1}^{\min(n,m)}d^k[\gcd(i,j)=d]\\=&\sum_{d=1}^{\min(n,m)}d^k\sum_{i=1}^{\lfloor\frac nd\rfloor}\sum_{j=1}^{\lfloor\frac md\rfloor}[\gcd(i,j)=1]\\=&\sum_{d=1}^{\min(n,m)}d^k\sum_{i=1}^{\lfloor\frac nd\rfloor}\sum_{j=1}^{\lfloor\frac md\rfloor}\sum_{x|\gcd(i,j)}\mu(x)\\=&\sum_{d=1}^{\min(n,m)}d^k\sum_{i=1}^{\lfloor\frac nd\rfloor}\sum_{j=1}^{\lfloor\frac md\rfloor}\sum_{x|i,x|j}\mu(x)\\=&\sum_{d=1}^{\min(n,m)}d^k\sum_{x=1}^{\min(\lfloor\frac nd\rfloor,\lfloor\frac md\rfloor)}\mu(x)\sum_{i=1}^{\lfloor\frac nd\rfloor}[x|i]\sum_{j=1}^{\lfloor\frac md\rfloor}[x|j]\\=&\sum_{d=1}^{\min(n,m)}d^k\sum_{x=1}^{\min(\lfloor\frac nd\rfloor,\lfloor\frac md\rfloor)}\mu(x)\lfloor\frac n{dx}\rfloor\lfloor\frac m{dx}\rfloor\end{align*} \]

\(p=dx\),则原式等于

\[\sum_{p=1}^{\min(n,m)}\lfloor\frac n{p}\rfloor\lfloor\frac m{p}\rfloor\sum_{d|p}d^k\mu(\frac pd) \]

显然前面的\(\lfloor\frac n{p}\rfloor\lfloor\frac m{p}\rfloor\)部分可以分块求解。

现在考虑后面的一部分,令

\[g(n)=\sum_{d|n}d^k\mu(\frac nd) \]

容易得出这个函数是积性函数,所以我们就可以线性筛然后求出其前缀和

然后就做完了

/*
author:loceaner
莫比乌斯反演
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;

const int a = 5e6 + 11;
const int b = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

bool vis[a];
int t, n, m, k, f[a], g[a], p[a], cnt, sum[a];

inline int power(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = res * a % mod;
		a = a * a % mod, b >>= 1;
	}
	return res;
}

inline int mo(int x) {
	if(x > mod) x -= mod;
	return x;
}

inline void work() {
	g[1] = 1;
	int maxn = 5e6 + 1;
	for (int i = 2; i <= maxn; i++) {
		if (!vis[i]) { p[++cnt] = i, f[cnt] = power(i, k), g[i] = mo(f[cnt] - 1 + mod); }
		for (int j = 1; j <= cnt && i * p[j] <= maxn; j++) {
			vis[i * p[j]] = 1;
			if (i % p[j] == 0) { g[i * p[j]] = g[i] * 1ll * f[j] % mod; break; }
			g[i * p[j]] = g[i] * 1ll * g[p[j]] % mod;
		}
	}
	for (int i = 2; i <= maxn; i++) g[i] = (g[i - 1] + g[i]) % mod;
}

inline int abss(int x) {
	while (x < 0) x += mod;
	return x;
}

signed main() {
	t = read(), k = read();
	work();
	while (t--) {
		n = read(), m = read();
		int maxn = min(n, m), ans = 0;
		for (int l = 1, r; l <= maxn; l = r + 1) {
			r = min(n / (n / l), m / (m / l));
			(ans += abss(g[r] - g[l - 1]) * 1ll * (n / l) % mod * (m / l) % mod) %= mod;
		}
		ans = (ans % mod + mod) % mod;
		cout << ans << '\n';
	}
	return 0;
}

上一篇:

下一篇: