「MtOI2019」幽灵乐团 题解
「MtOI2019」幽灵乐团 题解
莫比乌斯反演基础练习题题卡常!
算法标签
莫比乌斯反演,快速幂,逆元
题面
分析题面
给定 \(A, B, C\) ,求以下三个式子的值
多组询问
\(T \le 10^4, 1 \le A,B,C \le 10^5\)
问题分析
做本题前请先熟悉一般莫比乌斯反演的套路
我会尽量写得详细一点的(
如果看不清公式请尝试放大网页(
毫无疑问可以当作三道题做
先拆 \(\text{lcm}\)
于是每个问题都可以拆成两部分子问题:
-
\(F(A, B, C) = \prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C i\)
-
\(G(A, B, C) = \prod_{i = 1} ^ A \prod_{j = 1}^B \prod_{k = 1}^C \gcd(i, j)\)
于是三个问题都可转化为
的形式
接下来,只需求出每种问题所对应的 \(F,G\) ,就能够解决该题
在这之前,为了更好理解,有一个基本的等价转化:
显然这里的 \(A\) 不能与 \(i\) 有关
\(\texttt{Type 1}\)
先考虑 \(F\)
预处理 \(i\) 的阶乘即可
考虑 \(G\)
套路:枚举 \(\gcd\),假设 \(A < B\)
内层 \(g\) 与 \(i, j\) 都无关了,可以继续向上甩
嵌套式莫反
套路:枚举约数 \(d\)
套路:枚举 \(T = gd\)
然后这个 \(\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\)
记 \(S_n = \sum_{i = 1} ^ n i = \frac {n (n + 1)} 2\)
则
预处理 \(i^i\) 的阶乘即可
考虑 \(G\)
套路:枚举 \(\gcd\)
嵌套式莫反
套路:枚举约数 \(d\)
套路:枚举 \(T = gd\)
同样,\(\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}\)
对于这一部分有一种神奇的做法
取 \(\ln\)
套路:枚举 \(\gcd\)
嵌套式莫反
套路:枚举约数 \(d\)
套路:枚举 \(T = gd\)
思路比较清晰了,后面那一堆有点像 \(\texttt{Type 1}\) 的情况,但主要是前面那一堆,还是类埃筛预处理?
其实不用,观察这一堆
由于之前的 \(\prod\) 变成了 \(\sum\) ,于是变成了标准的莫比乌斯反演形式
那么根据莫比乌斯反演的性质,如果令 \(f(T) = \sum_{d | T}(\frac T d) \mu(d)\) ,\(f\) 将为积性函数
于是我们可以用线性筛预处理
再进一步观察,其实这个 \(f\) 是我们所熟悉的一个函数
上面这个式子不就是 \(id * \mu\) 吗(其中 \(*\) 表示狄利克雷卷积)
根据欧拉反演,\(\varphi = id * \mu\),所以 \(f\) 其实就是 \(\varphi\)
别忘了我们这个式子是取了 \(\ln\) 的,再 \(\exp\) 回去
内层就是 \(\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;
}
总结
- 真·莫比乌斯反演基础练习题
- 要想理解得更清楚,还是建议自己手推,中间推到某个地方不知所措时再看题解,这样学习的效果更好(真实感受!)
- 说实话我最开始也不知道 \(\texttt{Type 3}\) 里面的那个东西是欧拉反演,只是看着题解直接跳过莫反凭空出现一个 \(\varphi\) 很奇怪,最后自己手推验证,并 bdfs 才知道了欧拉反演这回事
(之前学筛法的时候为怎么没学这玩意!),另外,我认知范围内的欧拉反演是 \(n = \sum_{d | n} \varphi(d)\),涨知识了/hanx - 又是一道做了一晚上的题/ll

该文不被密码保护。
浙公网安备 33010602011771号