BZOJ 2301: [HAOI2011]Problem b (莫比乌斯反演)

本文介绍了一种使用数论技巧和莫比乌斯反演来解决区间内最大公约数(GCD)计数问题的方法。通过分块技术和容斥原理优化计算过程,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

有多少对 x属于[a,b] , y属于[c,d], 满足 gcd(x,y)

思路:

直接对于[a,b],[c,d]中的每个gcd(x,y)=k的倍数进行容斥,

1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000  不记莫比乌斯打表的时间,

也会T

看了不少博客,这个题目用了些技巧。


分块:

以前求用过这里两个数就不那么容易想到了,我们知道由于整形除法的一些性质,

[n/2+1,n],[n/3+1,n/2],[n/4+1,n/3],n/i都是定值

同样,两个数的时候也用一定的区间,[l,r] 会有  a/i  ,b/i同时是定值

这样我们求出gcd(x,y)=i 的个数都将等于 (a/i)*(b/i),

系数莫比乌斯函数项,我们可以用去区间和的

在[l,r]  ans=sigma(mu[i]*(a/i)*(b/i))=[sigma(mu[i])]*(a/i)*(b/i);

求出[sigma(mu[i])]只要前缀和相减就行

那么两个数怎么分块呢?

注意到
比如 l=100, r=200   
那么 i=34 。。。。。        40   (l / i) * (r / i)都是一样的 2*5 
那么 i=41 。。。。。        50   (l / i) * (r / i)都是一样的 2*4
那么 i=51 。。。。。        66   (l / i) * (r / i)都是一样的 1*3
 34-40,41-50 51-66 是怎么来的呢? 
 34 = 33+1;
 40 = min(l/(l/34) , r/(r/34));
 41 = 40+1;
 50 = min(l/(l/41) , r/(r/41));
 51 = 50+1;
 66 = min(l/(l/51) , r/(r/51));

我们看到(l/i=2)区间还没断,但是(r/i=5)的区间已经结束了

两个数把各自的分块区间打断了,但是好在时间复杂度并没用太大的改变


容斥:

分块可以降低很大的时间复杂度,但是明显不太好处理区间,我们通常都是求出[1,n]

的结果,所以我们可以用到容斥

ans([a,b],[c , d])

=ans([1,b],[1,d])-ans([1,b],[1,c-1])-ans([1,a-1],[1,c])+ans([1,a-1],[1,c-1]);



#include<bits/stdc++.h>
using  namespace std;
#define MAXN 50000
bool check[MAXN+10]; 
int prime[MAXN+10]; 
int mu[MAXN+10]; 
int sum[MAXN+10];
void Moblus() 
{ 
    memset(check,false,sizeof(check)); 
    mu[1]=1;
    int tot = 0; 
   
    for(int i = 2; i <= MAXN; i++) 
    { 
        if( !check[i] ) 
        { 
            prime[tot++] = i; 
            mu[i] = -1; 
        } 
        for(int j = 0; j < tot; j++) 
        { 
            if(i * prime[j] > MAXN) break; 
            check[i * prime[j]] = true; 
            if( i % prime[j] == 0) 
            { 
                mu[i * prime[j]] = 0; 
                break; 
            } 
            else 
            { 
                mu[i * prime[j]] = -mu[i]; 
            } 
        }
        
    } 
    sum[0]=0;
     for (int i = 1; i <= MAXN; i++)
    {
        sum[i] = sum[i - 1] + mu[i];
    }
} 
// 利用莫比乌斯反演 找[1, n], [1, m]内gcd(i,j)=1的数对的个数
/*
O(n) 枚举(1,l)会T 
ans += mob[i] * (l / i) * (r / i)
注意到
比如 l=100, r=200   
那么 i=34 。。。。。        40   (l / i) * (r / i)都是一样的 2*5 
那么 i=41 。。。。。        50   (l / i) * (r / i)都是一样的 2*4
那么 i=51 。。。。。        66   (l / i) * (r / i)都是一样的 1*3

当 i较大的时候,有些答案可以是mu[i]的前缀和乘以一个定值 
34-40,41-50 51-66 是怎么来的呢? 
 34 = 33+1;
 40 = min(l/(l/34) , r/(r/34));
 41 = 40+1;
 50 = min(l/(l/41) , r/(r/41));
 51 = 50+1;
 66 = min(l/(l/51) , r/(r/51));
 说白了,也就是找可以一起取定值的区间 
 其实也就类似在[n/2+1,n],[n/3+1,n/2]取的值是定值而可以减少计算
 这里有两个变量 ,两个变量的区间不同步,但是在一端小区间依然是定值 
*/

long long s(int n,int m)
{
    long long ans = 0;
    if (n > m)    swap(n, m);
    for (int i = 1, la = 0; i <= n; i = la + 1)
    {
        la = min(n / (n / i), m / (m / i));
        ans += (long long)(sum[la] - sum[i - 1]) * (n / i) * (m / i);
    }
    return ans;
}
int main()
{
     Moblus() ;
    int n;
    int a,b,c,d,k;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        long long ans=s(b/k,d/k)-s((a-1)/k,d/k)-s((c-1)/k,b/k)+s((a-1)/k,(c-1)/k);    
        printf("%lld\n",ans);
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值