洛谷P3455 [POI2007]ZAP-Queries 莫比乌斯反演+整除分块

洛谷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)\)

注意事项

  • 不开long long见祖宗~

总结

  • 我认为这一类题用到的仅仅是莫比乌斯函数的性质,要多做题才能有更深刻的理解

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;
}

猜你喜欢

转载自www.cnblogs.com/danzh/p/11296686.html