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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/89524062

题目描述

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

题解

先求 1 x 1-x 1 y 1-y 的满足 g c d ( x , y )   =   k gcd(x,y)\ =\ k 的数对:我们设 f ( i ) f(i) 表示 k g c d ( x , y ) k|gcd(x,y) 的方案书, g ( i ) g(i) 表示 g c d ( 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)

因此对于前者μ(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 ( i d ) f(i*d) .显然对于 n i d \lfloor \frac{n}{i*d}\rfloor 和对于 m i d \lfloor \frac{m}{i*d}\rfloor 会形成不同的序列,每次对于i求解两个右端点中较小点 = j =j 即可。

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

现在考虑原题,由于有范围限制,根据容斥原理就很容易就想到:
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)

然后代码就很好实现了:

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

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/89524062