洛谷P3455 [POI2007]ZAP-Queries:莫比乌斯反演入门题

原题链接:洛谷P3455 [POI2007]ZAP-Queries

题目描述

FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对 x , y x,y x,y,满足 x ≤ a , y ≤ b x\leq a,y\leq b xayb,并且 g c d ( x , y ) = d gcd(x,y)=d gcd(x,y)=d。FGD希望得到你的帮助。

输入格式

第一行一个整数 n ( 1 ≤ n ≤ 50000 n (1 \leq n \leq 50000 n(1n50000),代表询问个数。
接下来 n n n行每行三个整数: a, b, d ( 1 ≤ d ≤ a , b ≤ 50000 ) (1\leq d\leq a,b\leq 50000) (1da,b50000), 用空格分开。

输出格式

n n n行,每行一个整数,代表每次询问的答案。

输入输出样例
  • 输入1
2
4 5 2
6 4 3
  • 输出1
3
2
题解

此题为莫比乌斯反演入门题,让我们对莫比乌斯反演题是要我们做什么有个概念。有关莫比乌斯反演,可参考莫比乌斯反演及的证明与应用

我们设:
f ( x ) = ∑ i = 1 a ∑ j = 1 b [ g c d ( i , j ) = x ] , f(x)=\sum_{i=1}^{a}\sum_{j=1}^{b}[gcd(i,j)=x], f(x)=i=1aj=1b[gcd(i,j)=x],
F ( x ) = ∑ x ∣ k f ( k ) = ∑ i = 1 a ∑ j = 1 b [ x ∣ g c d ( i , j ) ] F(x)=\sum_{x|k}f(k)=\sum_{i=1}^{a}\sum_{j=1}^{b}[x|gcd(i,j)] F(x)=xkf(k)=i=1aj=1b[xgcd(i,j)]
= ∑ i = 1 a [ x ∣ i ] ∑ j = 1 b [ x ∣ j ] = ⌊ a x ⌋ ⌊ b x ⌋ =\sum_{i=1}^{a}[x|i]\sum_{j=1}^{b}[x|j]=\lfloor\frac{a}{x}\rfloor\lfloor\frac{b}{x}\rfloor =i=1a[xi]j=1b[xj]=xaxb
则可以由莫比乌斯反演可以推出:
f ( x ) = ∑ x ∣ k μ ( ⌊ k x ⌋ ) F ( k ) f(x)=\sum_{x|k}\mu(\lfloor\frac{k}{x}\rfloor)F(k) f(x)=xkμ(xk)F(k)
设完这两个函数之后,我们便发现, A n s = f ( d ) Ans=f(d) Ans=f(d)。那么,
A n s = ∑ d ∣ k μ ( ⌊ k d ⌋ ) F ( k ) Ans=\sum_{d|k}\mu(\lfloor\frac{k}{d}\rfloor)F(k) Ans=dkμ(dk)F(k)
t = k d t=\frac{k}{d} t=dk。我们枚举 t t t
A n s = ∑ t = 1 μ ( t ) ⌊ a t d ⌋ ⌊ b t d ⌋ Ans=\sum_{t=1}\mu(t)\lfloor\frac{a}{td}\rfloor\lfloor\frac{b}{td}\rfloor Ans=t=1μ(t)tdatdb
由于有多组数据,所以我们再用一下整除分块预处理,这样单次询问就可以 O ( n ) O(\sqrt{n}) O(n )了。要注意的是,当 t d > m i n ( a , b ) td>min(a,b) td>min(a,b)时就可以结束累加。

当然还有最后一个问题: ⌊ a t d ⌋ \lfloor\frac{a}{td}\rfloor tda含有一个常量 d d d,如何整除分块?
a = k t + b a=kt+b a=kt+b ( 0 ≤ b &lt; t ) (0\leq b&lt;t) (0b<t), k = p d + q k=pd+q k=pd+q ( 0 ≤ q &lt; d ) (0\leq q&lt;d) (0q<d), 那 ⌊ ⌊ a t ⌋ d ⌋ = ⌊ k d ⌋ = p \lfloor\frac{\lfloor\frac{a}{t}\rfloor}{d}\rfloor=\lfloor\frac{k}{d}\rfloor=p dta=dk=p
⌊ a t d ⌋ = ⌊ ( p d + q ) t + b t d ⌋ \lfloor\frac{a}{td}\rfloor=\lfloor\frac{(pd+q)t+b}{td}\rfloor tda=td(pd+q)t+b
= ⌊ p t d + ( t q + b ) t d ⌋ =\lfloor\frac{ptd+(tq+b)}{td}\rfloor =tdptd+(tq+b)
p + ⌊ t q + b t d ⌋ = p p+\lfloor\frac{tq+b}{td}\rfloor=p p+tdtq+b=p
因此 ⌊ a t d ⌋ = ⌊ ⌊ a t ⌋ d ⌋ = ⌊ ⌊ a d ⌋ t ⌋ , \lfloor\frac{a}{td}\rfloor=\lfloor\frac{\lfloor\frac{a}{t}\rfloor}{d}\rfloor=\lfloor\frac{\lfloor\frac{a}{d}\rfloor}{t}\rfloor, tda=dta=tda,
整除分块时块与块之间的距离不小于 ⌊ a t ⌋ \lfloor\frac{a}{t}\rfloor ta。当然,我们也可以先将 a , b a,b a,b分别除以 d d d之后再进行分块。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=50005;
int n,a,b,d;

int num[maxn],prime[maxn],p_num;
int mu[maxn];
int sum[maxn];
void get_mu()
{
    mu[1]=1;
    for(int i=2;i<maxn;i++)
    {
        if(!num[i])
        {
            prime[++p_num]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=p_num&&i*prime[j]<maxn;j++)
        {
            num[i*prime[j]]=1;
            if(i%prime[j]) mu[i*prime[j]]=-mu[i];
            else
            {
                mu[i*prime[j]]=0;
                break;
            }
        }
    }
    //预处理mu的前缀和。
    for(int i=1;i<maxn;i++) sum[i]=sum[i-1]+mu[i];
}


int main()
{
    scanf("%d",&n);
    get_mu();
    while(n--)
    {
        int ans=0;
        scanf("%d%d%d",&a,&b,&d);
        int up=min(a,b);
        //数论分块
        //另一种方法:
        //int ss=a/d,sss=b/d;
        for(int i=1,j;i*d<=up;i=j+1)
        {
            j=min(a/(a/i),b/(b/i));
            //另一种方法:
        	//j=min(ss/(ss/i),sss/(sss/i));
            ans+=(a/(i*d))*(b/(i*d))*(sum[j]-sum[i-1]);
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值