莫比乌斯反演学习笔记

前置知识

  • 莫比乌斯函数\(\mu(d)\):当\(𝑑=1\)时,\(𝜇(𝑑)=1\);当\(𝑑=\prod\limits_{i=1}^𝑘p_𝑖\)\(𝑝_𝑖\)为互异素数时,\(𝜇(𝑑)=(−1)^𝑘\)

  • 莫比乌斯函数性质:

    \(1.\)对于任意正整数\(𝑛\)\(\sum\limits_{𝑑|𝑛}\mu(𝑑)=[𝑛=1]\)

    \(2.\)对于任意正整数\(𝑛\)\(\sum\limits_{𝑑|𝑛}\frac{\mu(𝑑)}{𝑑}=\frac{\varphi(𝑛)}{n}\)

内容

第一种形式:已知\(𝐹(𝑥)=\sum\limits_{𝑑|𝑥}𝑓(𝑑)\),则\(𝑓(𝑥)=\sum\limits_{𝑑|𝑥}\mu(\frac{x}{d})𝐹(𝑑)\)

第二种形式:已知\(𝐹(𝑥)=\sum\limits_{x|d}^{n}𝑓(𝑑)\),则\(𝑓(𝑥)=\sum\limits_{x|d}^{n}\mu(\frac{d}{x})𝐹(𝑑)\)

题目

[Luogu] P3455 [POI2007]ZAP-Queries

\(Link\)

Description

\(n\)组询问,给出\(a,b,d\),求满足\(1 \leq x \leq a\)\(1 \leq y \leq b\),且\(\gcd(x,y)=d\)的二元组\((x,y)\)的数量。\((1≤n≤5×10^4,1 \leq d \leq a,b \leq 5 \times 10^4)\)

Solution

不妨设\(a\le{b}\),那么答案为

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

我们令\(x=x*d,y=y*d\),那么答案为

\[\sum_{x=1}^{\lfloor\frac{a}{d}\rfloor}\sum_{y=1}^{\lfloor\frac{b}{d}\rfloor}[\gcd(x,y)=1] \]

\[=\sum_{x=1}^{\lfloor\frac{a}{d}\rfloor}\sum_{y=1}^{\lfloor\frac{b}{d}\rfloor}\sum\limits_{k|\gcd(x,y)}\mu(k) \]

我们再令\(x=x*k,y=y*k\),那么答案为

\[\sum\limits_{k=1}^{\lfloor\frac{a}{d}\rfloor}\sum_{x=1}^{\lfloor\frac{a}{dk}\rfloor}\sum_{y=1}^{\lfloor\frac{b}{dk}\rfloor}\mu(k) \]

\[=\sum\limits_{k=1}^{\lfloor\frac{a}{d}\rfloor}\mu(k){\lfloor\frac{a}{dk}\rfloor}{\lfloor\frac{b}{dk}\rfloor} \]

用整除分块可以做到\(O(n\sqrt{a})\)

Code

#include <bits/stdc++.h>

using namespace std;

int t, x, y, d, tot, vis[50005], pr[12505], mu[50005], sum[50005];

int read()
{
	int x = 0, fl = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
	return x * fl;
}

int main()
{
	mu[1] = 1;
	for (int i = 2; i <= 50000; i ++ )
	{
		if (!vis[i])
		{
			vis[i] = i;
			pr[ ++ tot] = i;
			mu[i] = -1;
		}
		for (int j = 1; j <= tot; j ++ )
		{
			if (i * pr[j] > 50000 || pr[j] > vis[i]) break;
			vis[i * pr[j]] = pr[j];
			if (i % pr[j]) mu[i * pr[j]] = -mu[i];
			else mu[i * pr[j]] = 0;
		}
	}
	for (int i = 1; i <= 50000; i ++ )
		sum[i] = sum[i - 1] + mu[i];
	t = read();
	while (t -- )
	{
		x = read(), y = read(), d = read();
		x /= d; y /= d;
		int mn = min(x, y), res = 0;
		for (int l = 1, r; l <= mn; l = r + 1)
		{
			r = min(min(x / (x / l), y / (y / l)), mn);
			res = res + (sum[r] - sum[l - 1]) * (x / l) * (y / l);
		}
		printf("%d\n", res);
	}
	return 0;
}

[Luogu] P2257 YY的GCD

\(Link\)

Description

\(T\)组询问,给定\(n,m\),求\(1 \leq x \leq n,1 \leq y \leq m\)\(\gcd(x, y)\)为质数的\((x, y)\)有多少对。\(T=10^4,n,m\le{10^7}\)

Solution

不妨设\(n\le{m}\),那么答案为

\[\sum\limits_{p\in{P}}\sum_{x=1}^n\sum_{y=1}^m[\gcd(x,y)=p] \]

我们令\(x=x*p,y=y*p\),那么答案为

\[\sum\limits_{p\in{P}}\sum_{x=1}^{\lfloor\frac{n}{p}\rfloor}\sum_{y=1}^{\lfloor\frac{m}{p}\rfloor}\sum\limits_{k|\gcd(x,y)}\mu(k) \]

我们再令\(x=x*k,y=y*k\),那么答案为

\[\sum\limits_{p\in{P}}\sum\limits_{k=1}^{\lfloor\frac{n}{p}\rfloor}\mu(k){\lfloor\frac{n}{kp}\rfloor}{\lfloor\frac{m}{kp}\rfloor} \]

接下来好像有点不太好弄,我们改为枚举\(S=kp\),用\(S\)来表示\(k\)

那么答案为

\[\sum\limits_{S=1}^n{\lfloor\frac{n}{S}\rfloor}{\lfloor\frac{m}{S}\rfloor}\sum\limits_{p|S\land{p\in{P}}}\mu(\frac{S}{p}) \]

这样子是为了埃氏筛求出后面的部分。具体地,我们枚举所有质数,对于它的倍数,加上对应贡献。

用整除分块加预处理,可以做到\(O(n\ln(n)+T\sqrt{n})\)

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

int t, n, m, tot, vis[10000005], pr[2500005], mu[100000005], cnt[10000005];

ll sum[10000005];

int read()
{
	int x = 0, fl = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
	return x * fl;
}

int main()
{
	mu[1] = 1;
	for (int i = 2; i <= 10000000; i ++ )
	{
		if (!vis[i])
		{
			vis[i] = i;
			pr[ ++ tot] = i;
			mu[i] = -1;
		}
		for (int j = 1; j <= tot; j ++ )
		{
			if (i * pr[j] > 10000000 || pr[j] > vis[i]) break;
			vis[i * pr[j]] = pr[j];
			if (i % pr[j]) mu[i * pr[j]] = -mu[i];
			else mu[i * pr[j]] = 0;
		}
	}
	for (int i = 1; i <= tot; i ++ )
		for (int j = pr[i]; j <= 10000000; j += pr[i])
			cnt[j] += mu[j / pr[i]];
	for (int i = 1; i <= 10000000; i ++ )
		sum[i] = sum[i - 1] + (ll)cnt[i];
	t = read();
	while (t -- )
	{
		n = read(), m = read();
		int mn = min(n, m); ll res = 0ll;
		for (int l = 1, r; l <= mn; l = r + 1)
		{
			r = min(min(n / (n / l), m / (m / l)), mn);
			res = res + 1ll * (sum[r] - sum[l - 1]) * (n / l) * (m / l);
		}
		printf("%lld\n", res);
	}
	return 0;
}
posted @ 2020-11-12 15:26  andysj  阅读(122)  评论(0编辑  收藏  举报