「MtOI2019」幽灵乐团 题解

「MtOI2019」幽灵乐团 题解

莫比乌斯反演基础练习题题

卡常!

算法标签

莫比乌斯反演,快速幂,逆元

题面

「MtOI2019」幽灵乐团 - 题目

分析题面

给定 \(A, B, C\) ,求以下三个式子的值

\[\begin{aligned} &\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \frac {\text{lcm}(i, j)}{\gcd(i, k)} \\ &\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \begin{pmatrix} \frac {\text{lcm}(i, j)}{\gcd(i, k)} \end{pmatrix} ^{i \times j \times k} \\ &\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \begin{pmatrix} \frac {\text{lcm}(i, j)}{\gcd(i, k)} \end{pmatrix} ^{\gcd(i,j,k)} \\ \end{aligned} \]

多组询问

\(T \le 10^4, 1 \le A,B,C \le 10^5\)

问题分析

做本题前请先熟悉一般莫比乌斯反演的套路

我会尽量写得详细一点的(

如果看不清公式请尝试放大网页(

毫无疑问可以当作三道题做

先拆 \(\text{lcm}\)

\[\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \frac {i \times j}{\gcd(i, j) \gcd(i, k)} \]

于是每个问题都可以拆成两部分子问题:

  1. \(F(A, B, C) = \prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C i\)

  2. \(G(A, B, C) = \prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \gcd(i, j)\)

于是三个问题都可转化为

\[\begin{pmatrix} \frac {F(A, B, C) F(B, A, C)}{G(A, B, C)G(A, C, B)} \end{pmatrix} ^t \]

的形式

接下来,只需求出每种问题所对应的 \(F,G\) ,就能够解决该题

在这之前,为了更好理解,有一个基本的等价转化:

\[\prod_if(i)A=A^{\sum_if(i)} \]

显然这里的 \(A\) 不能与 \(i\) 有关


\(\texttt{Type 1}\)

先考虑 \(F\)

\[\begin{aligned} F(A, B, C) = &\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C i\\ =&\prod_{i = 1} ^ A \prod_{j = 1}^B i ^{\sum_{k = 1}^C1}\\ =&\prod_{i = 1} ^ A \prod_{j = 1}^B i^C\\ =&\prod_{i = 1} ^ Ai ^{BC}\\ =&(\prod_{i = 1} ^ Ai)^{BC} \end{aligned} \]

预处理 \(i\) 的阶乘即可

考虑 \(G\)

\[\begin{aligned} G(A, B, C) = &\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \gcd(i,j)\\ =&\prod_{i = 1} ^ A \prod_{j = 1}^B\gcd(i,j)^C \end{aligned} \]

套路:枚举 \(\gcd\),假设 \(A < B\)

\[\prod_{g=1}^A \prod_{i = 1} ^ {\frac A g} \prod_{j = 1}^{\frac B g}g^C[\gcd(i,j)=1] \]

内层 \(g\)\(i, j\) 都无关了,可以继续向上甩

\[\prod_{g=1}^A g^{C\sum_{i = 1} ^ {\frac A g} \sum_{j = 1}^{\frac B g}[\gcd(i,j)=1]} \]

嵌套式莫反

\[\prod_{g=1}^A g^{C \sum_{i = 1} ^ {\frac A g} \sum_{j = 1}^{\frac B g}\sum_{d|i, d|j}\mu(d)} \]

套路:枚举约数 \(d\)

\[\prod_{g=1}^A g^{C \sum_{d = 1}^{\frac A g} \mu(d) \frac A {gd} \frac B {gd}} \]

套路:枚举 \(T = gd\)

\[\prod_{T = 1} ^A \prod_{d | T} (\frac T d)^{C\mu(d)\frac A T \frac B T} \]

\[\begin{pmatrix} \prod_{T = 1} ^A \prod_{d | T} (\frac T d)^{\mu(d)\frac A T \frac B T} \end{pmatrix} ^C \]

\[\begin{pmatrix} \prod_{T = 1} ^A \begin{pmatrix} \prod_{d | T} (\frac T d) ^ {\mu(d)} \end{pmatrix} ^{\frac A T \frac B T} \end{pmatrix} ^C \]

然后这个 \(\prod_{d | T} (\frac T d) ^ {\mu(d)}\)

可以类埃筛预处理,时间复杂度 \(\mathcal O(n\log \log n + n \log n) \rightarrow \mathcal O(n \log n)\)

外面 \(\prod_{T = 1} ^ A () ^{\frac A T \frac B T}\) 可以数论分块 \(\mathcal O(\sqrt n)\)

于是每次询问 \(\mathcal O(\sqrt n \log n)\)


\(\texttt{Type 2}\)

考虑 \(F\)

\[\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C i ^ {i \times j \times k} \]

不断向上甩 \(\prod\)

\[\prod_{i = 1} ^ A i ^ {i\sum_{j = 1} ^ B j \sum_{k = 1}^C k} \]

\(S_n = \sum_{i = 1} ^ n i = \frac {n (n + 1)} 2\)

\[\prod_{i = 1} ^ A i ^ {iS_BS_C} \]

\[\begin{pmatrix} \prod_{i = 1} ^ A i ^ {i} \end{pmatrix} ^{S_BS_C} \]

预处理 \(i^i\) 的阶乘即可

考虑 \(G\)

\[\begin{aligned} \prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \gcd(i, j) ^ {i \times j \times k}\\ \prod_{i = 1} ^ A \prod_{j = 1}^B \gcd(i, j) ^ {i \times j \sum_{k = 1} ^ C k}\\ \prod_{i = 1} ^ A \prod_{j = 1}^B \gcd(i, j) ^ {i \times j \times S_C}\\ \end{aligned} \]

套路:枚举 \(\gcd\)

\[\begin{aligned} \begin{pmatrix} \prod_{g = 1} ^ A\prod_{i = 1} ^ {\frac A g} \prod_{j = 1}^ {\frac B g} g ^ {ig \times jg}[\gcd(i, j) = 1] \end{pmatrix} ^ {S_C} \\ \begin{pmatrix} \prod_{g = 1} ^ A g ^ { g^2\sum_{i = 1} ^ {\frac A g} i \sum_{j = 1}^ {\frac B g}j[\gcd(i, j) = 1]} \end{pmatrix} ^ {S_C}\\ \end{aligned} \]

嵌套式莫反

\[\begin{pmatrix} \prod_{g = 1} ^ A g ^ { g^2\sum_{i = 1} ^ {\frac A g} i \sum_{j = 1}^ {\frac B g}j\sum_{d | i, d| j} \mu (d)} \end{pmatrix} ^ {S_C} \]

套路:枚举约数 \(d\)

\[\begin{aligned} \begin{pmatrix} \prod_{g = 1} ^ A g ^ { g^2\sum_{i = 1} ^ {\frac A g} i \sum_{j = 1}^ {\frac B g}j\sum_{d | i, d| j} \mu (d)} \end{pmatrix} ^ {S_C} \\ \begin{pmatrix} \prod_{g = 1} ^ A g ^ { g^2\sum_{d = 1} ^ {\frac A g} \mu(d) \sum_{i = 1} ^ {\frac A {gd}} id \sum_{j = 1}^ {\frac B {gd}}jd } \end{pmatrix} ^ {S_C}\\ \begin{pmatrix} \prod_{g = 1} ^ A g ^ { g^2\sum_{d = 1} ^ {\frac A g} d ^ 2 \mu(d) \sum_{i = 1} ^ {\frac A {gd}} i \sum_{j = 1}^ {\frac B {gd}}j} \end{pmatrix} ^ {S_C}\\ \begin{pmatrix} \prod_{g = 1} ^ A g ^ { g^2\sum_{d = 1} ^ {\frac A g} d^2 \mu(d) S_{\frac A {gd}} S_{\frac B {gd}} } \end{pmatrix} ^ {S_C} \end{aligned} \]

套路:枚举 \(T = gd\)

\[\begin{aligned} \begin{pmatrix} \prod_{T = 1} ^ A \prod_{d| T} (\frac T d) ^ { T^2 \mu(d) S_{\frac A {T}} S_{\frac B {T}} } \end{pmatrix} ^ {S_C}\\ \begin{pmatrix} \prod_{T = 1} ^ A \begin{pmatrix} \begin{pmatrix} \prod_{d| T} (\frac T d) ^{\mu(d)} \end{pmatrix} ^ { T^2} \end{pmatrix} ^{S_{\frac A {T}} S_{\frac B {T}} } \end{pmatrix} ^ {S_C} \end{aligned} \]

同样,\(\begin{pmatrix}\prod_{d| T} (\frac T d) ^{\mu(d)}\end{pmatrix} ^ { T^2}\) 可类埃筛 \(\mathcal O(n \log n)\) 预处理

\(\prod _{i = 1} ^ A () ^{S_{\frac A {T}} S_{\frac B {T}} }\) 可以 \(\mathcal O(\sqrt n)\) 数论分块

时间复杂度 \(\mathcal O(\sqrt n \log n)\)


\(\texttt{Type 3}\)

对于这一部分有一种神奇的做法

\[\prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \begin{pmatrix} \frac {\text{lcm}(i, j)}{\gcd(i, k)} \end{pmatrix} ^{\gcd(i,j,k)} \]

\(\ln\)

\[\begin{aligned} \sum_{i = 1} ^ A \sum_{j = 1}^B \sum_{k = 1}^C \gcd(i,j,k)\frac {\text{lcm}(i, j)}{\gcd(i, k)} \\ \end{aligned} \]

套路:枚举 \(\gcd\)

\[\begin{aligned} \sum _{g = 1} ^ A \sum_{i = 1} ^ {\frac A g} \sum_{j = 1}^ {\frac B g} \sum_{k = 1}^ {\frac C g} g \frac {\text{lcm}(ig, jg)}{\gcd(ig, kg)}[\gcd(i, j, k) = 1] \\ \sum _{g = 1} ^ A g\sum_{i = 1} ^ {\frac A g} \sum_{j = 1}^ {\frac B g} \sum_{k = 1}^ {\frac C g}\frac {\text{lcm}(i, j)}{\gcd(i, k)}[\gcd(i, j, k) = 1] \\ \end{aligned} \]

嵌套式莫反

\[\begin{aligned} \sum _{g = 1} ^ A g\sum_{i = 1} ^ {\frac A g} \sum_{j = 1}^ {\frac B g} \sum_{k = 1}^ {\frac C g}\frac {\text{lcm}(i, j)}{\gcd(i, k)}\sum_{d|i, d|j, d|k} \mu(d) \\ \end{aligned} \]

套路:枚举约数 \(d\)

\[\begin{aligned} \sum _{g = 1} ^ A g \sum_{d = 1} ^{\frac A g} \mu(d) \sum_{i = 1} ^ {\frac A {gd}} \sum_{j = 1}^ {\frac B {gd}} \sum_{k = 1}^ {\frac C {gd}}\frac {\text{lcm}(id, jd)}{\gcd(id, kd)}\\ \sum _{g = 1} ^ A g \sum_{d = 1} ^{\frac A g} \mu(d) \sum_{i = 1} ^ {\frac A {gd}} \sum_{j = 1}^ {\frac B {gd}} \sum_{k = 1}^ {\frac C {gd}}\frac {\text{lcm}(i, j)}{\gcd(i, k)}\\ \end{aligned} \]

套路:枚举 \(T = gd\)

\[\begin{aligned} \sum _{T = 1} ^ A \sum_{d | T}(\frac T d) \mu(d) \sum_{i = 1} ^ {\frac A {T}} \sum_{j = 1}^ {\frac B {T}} \sum_{k = 1}^ {\frac C {T}}\frac {\text{lcm}(i, j)}{\gcd(i, k)}\\ \end{aligned} \]

思路比较清晰了,后面那一堆有点像 \(\texttt{Type 1}\) 的情况,但主要是前面那一堆,还是类埃筛预处理?

其实不用,观察这一堆

\[\sum_{d | T}(\frac T d) \mu(d) \]

由于之前的 \(\prod\) 变成了 \(\sum\) ,于是变成了标准的莫比乌斯反演形式

那么根据莫比乌斯反演的性质,如果令 \(f(T) = \sum_{d | T}(\frac T d) \mu(d)\)\(f\) 将为积性函数

于是我们可以用线性筛预处理

再进一步观察,其实这个 \(f\) 是我们所熟悉的一个函数

上面这个式子不就是 \(id * \mu\) 吗(其中 \(*\) 表示狄利克雷卷积)

根据欧拉反演,\(\varphi = id * \mu\),所以 \(f\) 其实就是 \(\varphi\)

\[\begin{aligned} \sum _{T = 1} ^ A \varphi(T) \sum_{i = 1} ^ {\frac A {T}} \sum_{j = 1}^ {\frac B {T}} \sum_{k = 1}^ {\frac C {T}}\frac {\text{lcm}(i, j)}{\gcd(i, k)}\\ \end{aligned} \]

别忘了我们这个式子是取了 \(\ln\) 的,再 \(\exp\) 回去

\[\begin{aligned} \prod_{T = 1} ^ A \begin{pmatrix} \prod_{i = 1} ^ {\frac A {T}} \prod_{j = 1}^ {\frac B {T}} \prod_{k = 1}^ {\frac C {T}}\frac {\text{lcm}(i, j)}{\gcd(i, k)} \end{pmatrix} ^{\varphi(T) } \\ \end{aligned} \]

内层就是 \(\texttt{Type 1}\) 的情况,\(\mathcal O (\sqrt n \log n)\) 求得

外侧整除分块,\(\mathcal O(\sqrt n)\)

总时间复杂度 \(\mathcal O(n \log n)\)


整个部分总时间复杂度为 \(\mathcal O(T(\sqrt n \log n + \sqrt n \log n + n \log n)) \rightarrow \mathcal O(Tn \log n)\)

常数比较大,需要卡卡常

实现细节

真·卡常技巧:预处理一切能预处理的逆元!

代码实现

#include <bits/stdc++.h>
using namespace std;
#define re register
#define i64 long long
// #define pair pair<int, int>
// #define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read()
{
	re int x = 0, f = 0;
	re char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = 1;c = getchar();}
	while (isdigit(c)) {x = (x << 3) + (x << 1) + c - 48;c = getchar();}
	return f ? -x : x;
}
inline double dead()
{
	long long s = 0, t = 0, f = 1, isp = 0, cnt = 0;
	char c = getchar();
	while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
	while(isdigit(c) || c == '.')
	{
		if (c == '.') isp = 1;
		else if (!isp) s = (s << 1) + (s << 3) + c - 48;
		else t = (t << 1) + (t << 3) + c - 48, cnt++;
		c = getchar();
	}
	return (pow(0.1, cnt) * t + s) * f;
}
inline string getstr()
{
	string res = "";
	re char ch = getchar();
	while (isspace(ch)) ch = getchar();
	while (!isspace(ch)) res.push_back(ch), ch = getchar();
	return res;
}
const int N = 1e5 + 5, inf = 0x3f3f3f3f;
const int T = read(), P = read();

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

int pri[N], num, mu[N];
bool vis[N];
int S[N];

i64 fac[N], f[N], invf[N];
namespace Type1
{
	inline int up(const int A, const int B, const int C)
	{
		return ksm(fac[A], 1ll * B * C % (P - 1));
	}
	inline int down(const int A, const int B, const int C)
	{
		int res = 1;
		for (re int l = 1, r, up = min(A, B); l <= up; l = r + 1)
		{
			r = min(A / (A / l), B / (B / l));
			res = 1ll * res * ksm(f[r] * invf[l - 1] % P, 1ll * (A / l) * (B / l) % (P - 1)) % P;
		}
		return ksm(res, C);
	}
	inline int Type1(const int A, const int B, const int C)
	{
		int fz = 1ll * up(A, B, C) * up(B, A, C) % P; // 计算 F
		int fm = 1ll * down(A, B, C) * down(A, C, B) % P; // 计算 G
		return 1ll * fz * ksm(fm, P - 2) % P;
	}
}

i64 mifac[N], g[N], invg[N];
namespace Type2
{
	inline int up(const int A, const int B, const int C)
	{
		return ksm(mifac[A], 1ll * S[B] * S[C] % (P - 1));
	}
	inline int down(const int A, const int B, const int C)
	{
		int res = 1;
		for (re int l = 1, r, up = min(A, B); l <= up; l = r + 1)
		{
			r = min(A / (A / l), B / (B / l));
			res = 1ll * res * ksm(g[r] * invg[l - 1] % P, 1ll * S[A / l] * S[B / l] % (P - 1)) % P;
		}
		return ksm(res, S[C] % (P - 1));
	}
	inline int Type2(const int A, const int B, const int C)
	{
		int fz = 1ll * up(A, B, C) * up(B, A, C) % P; // 计算 F
		int fm = 1ll * down(A, B, C) * down(A, C, B) % P; // 计算 G
		return 1ll * fz * ksm(fm, P - 2) % P;
	}
}

int phi[N];
namespace Type3
{
	inline int Type3(const int A, const int B, const int C)
	{
		int res = 1;
		for (re int l = 1, r, up = min(min(A, B), C); l <= up; l = r + 1)
		{
			r = min(min(A / (A / l), B / (B / l)), C / (C / l));
			res = 1ll * res * ksm(Type1::Type1(A / l, B / l, C / l), (phi[r] - phi[l - 1] + P - 1) % (P - 1)) % P;
		}
		return res;
	}
}

inline void sol(const int A, const int B, const int C)
{
	printf("%d ", Type1::Type1(A, B, C));
	printf("%d ", Type2::Type2(A, B, C));
	printf("%d\n", Type3::Type3(A, B, C));
}
int inv[N];
int A[72], B[72], C[72];
signed main()
{
	re int mx = 0;
	for (re int i = 1; i <= T; ++i)
		mx = max(mx, A[i] = read()), mx = max(mx, B[i] = read()), mx = max(mx, C[i] = read());

	mu[1] = 1, phi[1] = 1;
	for (re int i = 2; i <= mx; ++i)
	{
		if (!vis[i]) pri[++num] = i, mu[i] = -1, phi[i] = i - 1;
		for (re int j = 1; j <= num && i * pri[j] <= mx; ++j)
		{
			vis[i * pri[j]] = 1;
			if (i % pri[j] == 0) {phi[i * pri[j]] = phi[i] * pri[j]; break;}
			mu[i * pri[j]] = -mu[i], phi[i * pri[j]] = phi[i] * (pri[j] - 1);
		}
	}

	for (re int i = 1; i <= mx; ++i) S[i] = (S[i - 1] + i) % (P - 1);
	inv[0] = inv[1] = 1;
	for (re int i = 2; i <= mx; ++i) inv[i] = 1ll * (P - P / i) * inv[P % i] % P; // 卡常
	/*---------------------Type 1 & Type 2---------------------*/
	fac[1] = invf[0] = 1;
	mifac[1] = invg[0] = 1;
	for (re int i = 2; i <= mx; ++i) fac[i] = fac[i - 1] * i % P;
	for (re int i = 2; i <= mx; ++i) mifac[i] = mifac[i - 1] * ksm(i, i) % P;
	for (re int i = 0; i <= mx; ++i) f[i] = g[i] = 1;
	for (re int i = 1; i <= mx; ++i)
		for (re int j = i; j <= mx; j += i)
		{
			if (!mu[j / i]) continue;
			if (mu[j / i] == 1)
			{
				f[j] = f[j] * i % P;
				g[j] = g[j] * ksm(i, 1ll * j * j % (P - 1)) % P;	
			}
			else
			{
				f[j] = f[j] * inv[i] % P;
				g[j] = g[j] * ksm(inv[i], 1ll * j * j % (P - 1)) % P;
			}
		}
	for (re int i = 2; i <= mx; ++i) f[i] = f[i - 1] * f[i] % P;
	for (re int i = 1; i <= mx; ++i) invf[i] = ksm(f[i], P - 2); // 卡常
	for (re int i = 2; i <= mx; ++i) g[i] = g[i - 1] * g[i] % P;
	for (re int i = 1; i <= mx; ++i) invg[i] = ksm(g[i], P - 2); // 卡常
	/*---------------------Type 3---------------------*/
	for (re int i = 2; i <= mx; ++i) phi[i] = (phi[i] + phi[i - 1]) % (P - 1);

	for (re int i = 1; i <= T; ++i) sol(A[i], B[i], C[i]);
	return 0;
}

总结

  1. 真·莫比乌斯反演基础练习题
  2. 要想理解得更清楚,还是建议自己手推,中间推到某个地方不知所措时再看题解,这样学习的效果更好(真实感受!)
  3. 说实话我最开始也不知道 \(\texttt{Type 3}\) 里面的那个东西是欧拉反演,只是看着题解直接跳过莫反凭空出现一个 \(\varphi\) 很奇怪,最后自己手推验证,并 bdfs 才知道了欧拉反演这回事 (之前学筛法的时候为怎么没学这玩意!),另外,我认知范围内的欧拉反演是 \(n = \sum_{d | n} \varphi(d)\),涨知识了/hanx
  4. 又是一道做了一晚上的题/ll
posted @ 2022-06-09 11:07  After-glow  阅读(54)  评论(2)    收藏  举报