『莫比乌斯反演』「HAOI2011」Problem B

题目描述

对于给出的 n n n个询问,每次求有多少个数对 ( x , y ) (x,y) (x,y),满足 a ≤ x ≤ b a≤x≤b axb c ≤ y ≤ d c≤y≤d cyd,且 g c d ( x , y ) = k gcd(x,y) = k gcd(x,y)=k g c d ( x , y ) gcd(x,y) gcd(x,y)函数为 x x x y y y的最大公约数。

题解

先求 1 − x 1-x 1x 1 − y 1-y 1y的满足 g c d ( x , y )   =   k gcd(x,y)\ =\ k gcd(x,y) = k的数对:我们设 f ( i ) f(i) f(i)表示 k ∣ g c d ( x , y ) k|gcd(x,y) kgcd(x,y)的方案书, g ( i ) g(i) g(i)表示 g c d ( x , y ) = k gcd(x,y)=k gcd(x,y)=k的方案数。

根据莫比乌斯反演公式,则一定有:

f ( i )   =   ∑ d = 1 ⌊ n i ⌋   g ( i ∗ d )    ⟺    g ( i )   =   ∑ d = 1 ⌊ n i ⌋ μ ( d ) ∗ f ( i ∗ d ) f(i)\ =\ \sum_{d=1}^{\lfloor \frac{n}{i}\rfloor}\ g(i*d)\iff g(i)\ =\ \sum_{d=1}^{\lfloor \frac{n}{i}\rfloor}μ(d)*f(i*d) f(i) = d=1in g(id)g(i) = d=1inμ(d)f(id)

因此对于前者μ(d),我们可以使用前缀和来进行求解。对于后者,我们考虑除法分块来实现。

根据f数组的定义,我们可以知道 f ( i ∗ d )   = ⌊ n i ∗ d ⌋ ⌊ m i ∗ d ⌋ f(i*d)\ =\lfloor \frac{n}{i*d}\rfloor \lfloor \frac{m}{i*d}\rfloor f(id) =idnidm.

因此我们需要利用除法分块来求解 f ( i ∗ d ) f(i*d) f(id).显然对于 ⌊ n i ∗ d ⌋ \lfloor \frac{n}{i*d}\rfloor idn和对于 ⌊ m i ∗ d ⌋ \lfloor \frac{m}{i*d}\rfloor idm会形成不同的序列,每次对于i求解两个右端点中较小点 = j =j =j即可。

a n s ( x , y ) ans(x,y) ans(x,y)表示 1 − x , 1 − y 1-x,1-y 1x,1y的答案。

现在考虑原题,由于有范围限制,根据容斥原理就很容易就想到:
a n s   =   a n s ( b , d ) − a n s ( a − 1 , d ) − a n s ( b , c − 1 ) + a n s ( a − 1 , c − 1 ) ans\ =\ ans(b,d)-ans(a-1,d)-ans(b,c-1)+ans(a-1,c-1) ans = ans(b,d)ans(a1,d)ans(b,c1)+ans(a1,c1)

然后代码就很好实现了:

#include <bits/stdc++.h>

using namespace std;

const int N = 100000;
int Miu[N+10];
int vis[N+10];
int sum[N+10];
int prime[N+10];

void Find_Miu(void)
{
    int m = 0;
    Miu[1] = 1;
    for (int i=2;i<=N;++i)
    {
        if (vis[i] == 0) prime[++m] = i, Miu[i] = -1;
        for (int j=1;j<=m && i*prime[j]<=N;++j)
        {
            vis[i*prime[j]] = 1;
            if (i%prime[j] == 0)
            {
                Miu[i*prime[j]] = 0;
                break;
            }
            Miu[i*prime[j]] = -Miu[i]; 
        }
    }
    return; 
}

void Get_sum(void)
{
    for (int i=1;i<=N;++i)
        sum[i] = sum[i-1]+Miu[i];
    return;
}

long long get(int a,int b,int d)
{
    int j,n,m;
    n = a/d;
    m = b/d;
    long long ans = 0;
    for (int i=1;i<=min(n,m);i=j+1)//不要写成i++ 
    {
        j = min(n/(n/i),m/(m/i));
        ans += (long long)(sum[j]-sum[i-1])*(n/i)*(m/i);
    }
    return ans;
}

void work(void)
{
    int a,b,c,d,k;
    scanf("%d %d %d %d %d",&a,&b,&c,&d,&k);
    printf("%lld\n",get(b,d,k)-get(a-1,d,k)-get(b,c-1,k)+get(a-1,c-1,k));
    return;
}

int main(void)
{
    int n;
    Find_Miu();
    Get_sum();
    scanf("%d",&n);
    while (n -- ) work();
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值