洛谷P3455 [POI2007]ZAP-Queries
标签
前言
简明题意
- 给定\(n,m,d\),对于\(i<=n,j<=m\),问其中有多少对二元组\((i,j)\)使得\(gcd(i,j)==d\)
- \(n,m<=5e4\)
思路
- 首先用数学形式写出来,就是
\[\sum_{i=1}^n \sum_{j=1}^m[gcd(i,j)==d]\]
根据我们的套路,是要将\([gcd(i,j)==d]\)的式子用\([gcd(i,j)==1]\)替换的,这样才能用莫比乌斯函数性质\(\sum_{d|n} \mu(d)=[n==1]\)从而降低复杂度。于是,我们换成\([gcd(i,j)==1]\)的形式:
\[\sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac md]}[gcd(i,j)==1]\]
- 接下来,我们就用\(\sum_{d|n} \mu(d)=[n==1]\)去替换条件式:
\[\sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac md]}\sum_{d_0|gcd(i,j)} \mu(d_0)\]
- 对于\(gcd\)有一个性质就是:\(d_0|gcd(i,j)\iff d_0|i 且 d_0|j\),于是原式就变成:
\[\sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac md]}\sum_{d_0|i 且d_0|j} \mu(d_0)\]
- 将原式改为枚举\(d_0\),根据\(d_0|i 且d_0|j\)可知\(d_0 \in [1,min(i,j)]\)即\(d_0 \in [1,min([\frac nd],[\frac md])]\),所以原式:
\[\sum_{d_0=1}^{min([\frac nd],[\frac md])]} \sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac md]} \left( \mu(d_0)*[d_0|i 且d_0|j] \right)\]
- 我们又能惊奇的发现,\(\sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac md]}\)与\(\mu(d_0)\)无关,所以可以把\(\mu(d_0)\)提到\(\sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac nd]}\)前面,于是就成了
\[\sum_{d=1}^{min([\frac nd],[\frac md])]} \left( \mu(d_0) \sum_{i=1}^{[\frac nd]} \sum_{j=1}^{[\frac md]} [d_0|i 且d_0|j] \right)\]
- 然后,其实 \(\sum \limits_{i=1}^{[\frac nd]} \sum \limits_{j=1}^{[\frac md]} [d_0|i 且d_0|j]\iff[\frac n{dd_0}]*[\frac m{dd_0}]\) (具体原因请自行举例琢磨),于是原式就成了:
\[\sum_{d=1}^{min([\frac nd],[\frac md])]} \left( \mu(d_0)*[\frac n{dd_0}]*[\frac m{dd_0}] \right)\]
- 来到了这里,是不是发现很容易就能整除分块了,因为每一块的\([\frac n{dd_0}]*[\frac m{dd_0}]\)是一样的,那就相当于是这一块的\(\mu d_0\)之和乘以这一块的\([\frac n{dd_0}]*[\frac m{dd_0}]\)。我们预处理出\(\mu(d_0)\)的前缀和,然后\(O(1)\)查询每一块\(\mu(d_0)\)的前缀和,每组样例的复杂度就是\(O(\sqrt n)\)
注意事项
总结
- 我认为这一类题用到的仅仅是莫比乌斯函数的性质,要多做题才能有更深刻的理解
AC代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 5e4 + 10;
bool no_prime[maxn];
int prime[maxn], mu[maxn], pre[maxn];
int shai(int n)
{
int cnt = 0;
mu[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!no_prime[i])
prime[++cnt] = i, mu[i] = -1;
for (int j = 1; j <= cnt && prime[j] * i <= n; j++)
{
no_prime[prime[j] * i] = 1;
mu[prime[j] * i] = i % prime[j] == 0 ? 0 : -mu[i];
if (i % prime[j] == 0) break;
}
}
for (int i = 1; i <= n; i++)
pre[i] = pre[i - 1] + mu[i];
return cnt;
}
long long cal(int n, int m)
{
long long ans = 0;
int l = 1, r;
while (l <= m)
{
r = min(n / (n / l), m / (m / l));
ans += (long long)(pre[r] - pre[l - 1]) * (n / l) * (m / l);
l = r + 1;
}
return ans;
}
void solve()
{
shai(maxn - 10);
int t;
scanf("%d", &t);
while (t--)
{
int n, m, d;
scanf("%d%d%d", &n, &m, &d);
if (n < m) swap(n, m);
printf("%lld\n", cal(n / d, m / d));
}
}
int main()
{
solve();
return 0;
}