Skip to content

题目链接

转载自 walker

前置知识

  • 假设 x 能唯一分解为 p1e1p2e2pkek,莫比乌斯函数 μ(x) 定义为:
μ(x)={0,ei>11,k是偶数1,k是奇数μ(x)={1,n=1(1)k,n=p1p2pk0,其他

题目分析

给定 a,b,d,求满足 1xa,1yb,并且 gcd(x,y)=d 的数对 (x,y) 个数 等价于 求满足 1xad1ybd,并且 gcd(x,y)=1 的数对 (x,y) 个数,即求两个区间内有多少对这样的数对是互质的。

证明:假设 gcd(xd,yd)>1,那么 gcd(x,y)>d,矛盾。

可以用容斥原理求解,设 ad=abd=b ,接下来统计不合法的对数:

有一个质公因子的方案数量:

a2b2+a3b3+a5b5+

有两个质公因子的方案数量:

a2×3b2×3+a2×5b2×5+

有三个质公因子的方案数量:

a2×3×5b2×3×5+

所以根据容斥原理,合法方案数为:

ans=aba2b2a3b3(只含一个质公因子)+a2×3b2×3+(只含两个质公因子)a2×3×5b2×3×5(只含三个质公因子)

注意到符号项就是莫比乌斯函数,故合法方案数可用莫比乌斯函数表示:

ans=ab+i=2min{a,b}aibimobius[i]

注意,1x 中因子 d 的个数就是 xd 个,不仅只有质因子有这样的性质

如果直接枚举 i,由于 a,b5×104,复杂度是 O(n) 级别,并且询问个数有 105,直接枚举是 O(n2) 级别,考虑优化。

我们发现,这个式子理论上 i 最多需要枚举 N 次,但实际上因为整除的缘故,有很多 i 对应的 aibi 都是相同的,所以是冗余枚举。由于 i 从小到大枚举,ai 都是单调递减的,并且理论上只有 2a 个不同的值(bi 同样)。这里用到了 AcWing 199. 余数之和 中的知识。


分块证明:

分段讨论:

  • ik 时,因为 i[1,k] 中的整数,所以只有 k 个不同的 ki 值。
  • i>k 时,kik,又因为式子取整了,所以式子只能取到 [1,k] 的值,故最多也只有 k 个不同的 ki 值。

有关当 i>k 时,kik 的证明:

由于下取整,所以 kik,假设 ki>k,那么 kik>k2=k,矛盾。

图和证明引自:墨染空


我们现在已经证明出了,可以将一段 长度为 n 的序列分成 2n 段的序列,如果我们能做到 O(1) 处理每一段,那么就可以将原先 O(n2) 的复杂度优化至 O(nn)

假设有 ax,假设此时的 x 是下界,求能使这个值不变的,最大的 x 为多少(上界),记这个上界是 g(x)。举个例子,249,值为 2,其中 9 是下界,那么上界就是 12,因为 2413=1。所以有 ax=ag(x),且 ag(x)>ag(x)+1。这里的 g(x) 实际上是有公式的:

g(x)=aax

g(x) 公式证明:

我们要证明的是 ag(x)=ax,且 ax>ag(x)+1,只有满足这个条件,才能证明 g(x) 是使这个值不变的上界

由于:

g(x)=aaxaax=x=x(xZ)

g(x)x,所以 ag(x)ax。又因为

aaaxaaax=ax

所以 ag(x)ax,第一部分证毕。

用带余除法证明第二部分:

a=kx+r, 则 0r<x,并且用第一部分证得的结论,代入第二部分式子得:

ax=k,ag(x)+1=aak+1

所以等价于证明

k>aak+1k>aak+1,kZ

等价于证明

k(ak+1)>a

再令 a=pk+q,则 0q<k,等价于证明:

k(p+1)>pk+qkp+k>pk+qk>q

由于 0q<k,因此得证。

代码实现

cpp
// Author: CodeBoy

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Re register
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) > (b) ? (b) : (a))
using namespace std;
typedef long long ll;
const int N = 50010;

int primes[N], cnt;
bool st[N];
int mobius[N], sum[N];

void init(int n)
{
    mobius[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        if (!st[i])
        {
            primes[cnt++] = i;
            mobius[i] = -1;
        }
        for (int j = 0; primes[j] * i <= n; j++)
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                mobius[t] = 0;
                break;
            }
            mobius[t] = mobius[i] * -1;
        }
    }

    for (int i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + mobius[i];
}

int main()
{
    init(N - 1);
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int a, b, d;
        scanf("%d%d%d", &a, &b, &d);
        a /= d, b /= d;
        int n = min(a, b);
        ll res = 0;
        for (int l = 1, r; l <= n; l = r + 1)
        {
            r = min(n, min(a / (a / l), b / (b / l)));
            res += (sum[r] - sum[l - 1]) * (ll)(a / l) * (b / l);
        }
        printf("%lld\n", res);
    }

    return 0;
}