【题解】P5348 密码解锁
莫反套路多合一,记了记了。
思路来源于仙人 command_block.
思路
莫比乌斯反演 而非 \(\mu\) 反演。
首先考虑到令需要求的数列为 \(a\),\(\mu(x) = \sum\limits_{x \mid i} a_i\).
令 \(G(x) = \sum\limits_{x \mid i} a_i\),根据倍数反演得:\(G(x) = \sum\limits_{x \mid i} \mu(x) \mu(\frac{i}{x})\).
根据套路,令 \(k = \frac{i}{x}\),考虑求 \(f(m)\)。有: \(f(m) = \sum\limits_{i = 1}^{\lfloor \frac{n}{m} \rfloor} \mu(i) \mu(im)\).
根据套路,\(\mu(xy) = \mu(x) \mu(y) [\gcd(x, y) = 1]\),于是原式等价于:\(\sum\limits_{i = 1}^{\lfloor \frac{n}{m} \rfloor} \mu^2(i) \mu(m) [\gcd(i, m) = 1]\).
设 \(G(n, m) = \sum\limits_{i = 1}^n \mu^2(i) [\gcd(i, m) = 1]\).
原式根据 \(\mu\) 反演得:\(\sum\limits_{i = 1}^n \mu^2(i) \sum\limits_{d \mid \gcd(i, m)} \mu(d)\).
等价于:\(\sum\limits_{i = 1}^n \mu^2(i) \sum\limits_{d \mid i, d \mid m} \mu(d)\).
交换求和顺序得:\(\sum\limits_{d = 1}^n \mu(d) \sum\limits_{d \mid i} \mu^2(i) = \sum\limits_{d = 1}^n \mu(d) \sum\limits_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \mu^2(id)\).
根据 \(\mu^2(xy) = \mu^2(x) \mu^2(y) [\gcd(x, y) = 1]\) 得:\(\sum\limits_{d = 1}^n \mu^3(d) \sum\limits_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \mu^2(i) [\gcd(i, d) = 1]\).
因为 \(G(\lfloor \frac{n}{d} \rfloor, d) = \sum\limits_{i = 1}^{\lfloor \frac{n}{d} \rfloor} \mu^2(i) [\gcd(i, d) = 1]\),所以原式等于 \(\sum\limits_{d = 1}^n \mu^3(d) G(\lfloor \frac{n}{d} \rfloor, d)\).
这个式子是可以递归算的,但是时间复杂度不明确。
边界是 \(G(n, 0) = 0, G(n, 1) = \sum\limits_{i = 1}^n \mu^2(i)\),其中 \(G(n, 1)\) 可以用 SP4168 的方式杜教筛求。
考虑加上一些优化:
-
预处理大数的质因子集合,将枚举因数转化成枚举子集。
-
考虑只处理 \(\mu(i) \neq 0\) 的 \(i\).
时间复杂度需要积分算,可以认为在 \(O(n^{\frac{5}{9}})\) 左右,具体不太会证。
此题还有我校仙人学长 yww 的题解,orz yww
代码
#include <cstdio>
#include <cmath>
#include <vector>
#include <bitset>
#include <map>
using namespace std;
typedef long long ll;
const int sz = 5e5 + 5;
const int st_sz = 7e2 + 5;
int t, m;
int tot, lim = 5e5;
int mu[sz], sum[sz], st_num[st_sz];
ll n;
ll st_mu[st_sz];
vector<int> pr;
bitset<sz> vis;
map<int, int> mp;
void init()
{
vis[1] = true, mu[1] = 1;
for (int i = 2; i <= lim; i++)
{
if (!vis[i]) mu[i] = -1, pr.push_back(i);
for (int j = 0; (j < pr.size()) && (i * pr[j] <= lim); j++)
{
vis[i * pr[j]] = true;
if (i % pr[j] == 0) { mu[i * pr[j]] = 0; break; }
else mu[i * pr[j]] = -mu[i];
}
}
for (int i = 1; i <= lim; i++) sum[i] = sum[i - 1] + (int)(mu[i] != 0), mu[i] += mu[i - 1];
st_mu[0] = st_num[0] = 1;
for (int i = 1; i < (1 << 9); i++) st_mu[i] = (i & 1) ? -st_mu[i >> 1] : st_mu[i >> 1];
}
int getS(int n)
{
if (n <= lim) return sum[n];
if (mp.count(n)) return mp[n];
int sum = 0;
for (int l = 1, r; l <= n; l = r + 1)
{
if (n / (l * l) == 0) break;
r = sqrt(n / (n / (l * l)));
sum += n / (l * l) * (mu[r] - mu[l - 1]);
}
return mp[n] = sum;
}
ll getG(int n, int st)
{
if (!n) return 0;
ll ans = 0;
for (int i = st; i; i = (i - 1) & st) ans += st_mu[i] * getG(n / st_num[i], i);
ans += getS(n);
return ans;
}
bool prework()
{
int tmp = m, cnt = 0;
tot = 0;
for (int i = 2; i * i <= tmp; i++)
{
if (tmp % i == 0)
{
for (int j = 0; j < (1 << tot); j++) st_num[j | (1 << tot)] = st_num[j] * i;
tot++, cnt = 0;
while (tmp % i == 0) tmp /= i, cnt++;
if (cnt > 1) return false;
}
}
if (tmp > 1)
{
for (int i = 0; i < (1 << tot); i++) st_num[i | (1 << tot)] = st_num[i] * tmp;
tot++;
}
return true;
}
int main()
{
init();
scanf("%d", &t);
while (t--)
{
scanf("%lld%d", &n, &m);
n /= m;
if (!prework()) { puts("0"); continue; }
printf("%lld\n", st_mu[(1 << tot) - 1] * getG(n, (1 << tot) - 1));
}
return 0;
}