luogu P3455 [POI2007]ZAP-Queries (莫比乌斯反演 + 整除分块)

整理的算法模板合集: ACM模板


题目传送门

在这里插入图片描述
在这里插入图片描述
本题中数据为5e4,我们只需要筛一次5e4就行了。
双倍经验的P4450 双亲数中数据达到了1e6,我们直接筛1e6的莫比乌斯函数有点不可取,因为只有一组数据,所以我们直接筛一次 m i n ( a , b ) min(a, b) min(a,b)即可。

  • f ( n ) f(n) f(n)表示规定范围内 g c d ( x , y ) = n gcd(x,y)=n gcd(x,y)=n的数对个数
  • F ( n ) F(n) F(n)表示规定范围内公约数包括 n n n 的数对个数(即 n ∣ g c d n|gcd ngcd的数对个数),也可以写成 F ( t ) = F(t)= F(t)=满足 g c d ( x , y ) % n = = 0 gcd(x,y)\%n==0 gcd(x,y)%n==0的数对个数

写成公式:

f ( k ) = ∑ i = 1 a ∑ j = 1 b [ g c d ( i , j ) = k ] f(k)=\sum_{i=1}^{a}\sum_{j=1}^{b}[gcd(i,j)=k] f(k)=i=1aj=1b[gcd(i,j)=k]

易得 F ( n ) F(n) F(n)

F ( n ) = ∑ n ∣ k f ( k ) F(n)=\sum_{n|k}f(k) F(n)=nkf(k)

然后我们就需要找到一个求解 F ( n ) F(n) F(n) 的方法即可。

在这里插入图片描述

然后我们就可以发现答案实际上就是: f ( d ) f(d) f(d)

我们利用莫比乌斯反演的第二种形式:

f ( d ) = ∑ d ∣ k μ ( ⌊ k d ⌋ ) F ( k ) f(d)=\sum_{d|k}\mu(\lfloor\frac{k}{d}\rfloor)F(k) f(d)=dkμ(dk)F(k)

f ( d ) = ∑ d ∣ k μ ( ⌊ k d ⌋ ) ⌊ a k ⌋ ⌊ b k ⌋ f(d)=\sum_{d|k}\mu(\lfloor\frac{k}{d}\rfloor)\lfloor\frac{a}{k}\rfloor\lfloor\frac{b}{k}\rfloor f(d)=dkμ(dk)kakb

枚举 ⌊ k d ⌋ \lfloor\frac{k}{d}\rfloor dk 设为 t t t
f ( d ) = ∑ t = 1 m i n ( a , b ) μ ( t ) ⌊ a t d ⌋ ⌊ b t d ⌋ f(d)=\sum_{t=1}^{min(a,b)}\mu(t)\lfloor\frac{a}{td}\rfloor\lfloor\frac{b}{td}\rfloor f(d)=t=1min(a,b)μ(t)tdatdb

时间复杂度为 O ( n ) O(n) O(n)我们再利用整除分块将时间复杂度降到 O ( n ) O(\sqrt{n}) O(n )。我们直接枚举 t t t,也就是说 l l l 就是 t t t

因为区间 l l l r r r的值都相同,所以我们使用整除分块,利用前缀和直接求莫比乌斯函数这一段的和。

推完公式代码就非常简单了:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>

using namespace std;
typedef long long ll;
const int N = 50007, M = 50007, INF = 0x3f3f3f3f;
const double eps = 1e-6;

int n, m;
int vis[N];
int cnt, mu[N], prime[N];
int sum[N];

void get_mu(int n)
{
    memset(vis, 0, sizeof vis);
    memset(mu, 0, sizeof mu);
    cnt = 0, mu[1] = 1;
    for(int i = 2; i <= n; ++ i){
        if(!vis[i]){
            prime[cnt ++ ] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < cnt && prime[j] * i <= n; ++ j){
            vis[prime[j] * i] = true;
            if(i % prime[j] == 0)break;
            mu[i * prime[j]] = - mu[i];
        }
    }
    for(int i = 1; i <= n; ++ i){
        sum[i] = sum[i - 1] + mu[i];
    }
}

int main()
{
    get_mu(5e4 + 7);
    scanf("%d", &n);
    while(n -- ){
        int a, b, d;
        ll ans = 0;
        scanf("%d%d%d", &a, &b, &d);
// l 从一定要1开始!从0开始的话就会 除0 直接RE
//要保证l <= min(a, b)因为一旦t大于a,就会除0(l就是枚举的t,t=k/d)
        for(int l = 1, r; l <= min(a, b); l = r + 1){
            r = min(a / (a / l), b / (b / l));
            ans += 1ll * (a / (l * d)) * (b / (l *d)) * (sum[r] - sum[l - 1]);
        }
        printf("%lld\n", ans);
    }
}
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页