hdu 1695 GCD(莫比乌斯反演经典入门||容斥原理+欧拉函数)

GCD

 Given 5 integers: a, b, c, d, k, you're to find x in a...b, y in c...d that GCD(x, y) = k. GCD(x, y) means the greatest common divisor of x and y. Since the number of choices may be very large, you're only required to output the total number of different number pairs.
Please notice that, (x=5, y=7) and (x=7, y=5) are considered to be the same.

Yoiu can assume that a = c = 1 in all test cases.

Input
The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 3,000 cases.
Each case contains five integers: a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000, as described above.
Output
For each test case, print the number of choices. Use the format in the example.
Sample Input

2
1 3 1 5 1
1 11014 1 14409 9

Sample Output

Case 1: 9
Case 2: 736427

Hint

For the first sample input, all the 9 pairs of numbers are (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 5), (3, 4), (3, 5).

题意:

题意:
给你5的数a,b,c,d,k ,其中a和c默认为1,求[a,b]和[c,d]两个区间有多少对数(x,y)使得gcd(x,y)==k,其中x属于[a,b],y属于[c,d]。

分析:

方法1:容斥原理+欧拉函数

根据题意让求在区间 [a,b],[c,d] [ a , b ] , [ c , d ] 中选两个数 x,y x , y 使得 gcd(x,y)=k g c d ( x , y ) = k

可以求 gcd(xk,yk)=1 g c d ( x k , y k ) = 1

因此问题转化成了求区间 [1,bk],[1,dk] [ 1 , b k ] , [ 1 , d k ] 中选两个数使得两个数互质的个数

为了防止重复,可以把区间分成两部分的和即 [1,bk][bk+1,dk] [ 1 , b k ] ⋃ [ b k + 1 , d k ]

对于前一个区间我们完全可以通过欧拉函数求解,计算前缀和便是前一个区间的互质个数,同时消除了重复的影响,因为这个区间只计算了一次

对于后一个区间我们利用容斥原理枚举 [bk+1,dk] [ b k + 1 , d k ] 中的每个数,然后和 bk b k 进行容斥

之后的内容就和HDU 2841 Visible Trees的做法一样了。

当然了,也完全可以不使用欧拉函数在一开始就用容斥定理,计算区间 [1,bk],[1,dk] [ 1 , b k ] , [ 1 , d k ] 中的互质数对个数,那么和上面那个HDU2841的做法就一模一样了。不解释了

解法1:

#include 
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll phi[maxn];
void init(){
    phi[1] = 1;
    for(ll i = 2; i < maxn; i++){
        phi[i] = i;
    }
    for(ll i = 2; i < maxn; i++){
        if(phi[i] == i){
            for(ll j = i; j < maxn; j += i){
                phi[j] = phi[j] / i * (i - 1);
            }
        }
    }
    for(ll i = 2; i < maxn; i++){
        phi[i] += phi[i-1];
    }
}
ll prime[maxn],cnt;
ll solve(ll m,ll n){
    cnt = 0;
    for(ll i = 2; i * i <= n; i++){
        if(n % i == 0){
            prime[cnt++] = i;
            while(n % i == 0) n /= i;
        }
    }
    if(n > 1) prime[cnt++] = n;
    ll res = 0;
    for(ll i = 1; i < (1<1,num = 0;
        for(ll j = 0; j < cnt; j++){
            if(i & (1<if(num & 1) res += m / tmp;
        else res -= m / tmp;
    }
    return m - res;
}
int main(){
    init();
    ll t,a,b,c,d,k,cas = 0;
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
        if(k == 0 || k > b || k > d){
            printf("Case %lld: 0\n",++cas);
            continue;
        }
        b /= k;
        d /= k;
        if(b > d) swap(b,d);
        ll ans = 0;
        for(ll i = b+1; i <= d; i++){
            ans += solve(b,i);
        }
        printf("Case %lld: %lld\n",++cas,phi[b]+ans);
    }
    return 0;
}

方法二:莫比乌斯反演

其实更想写一下莫比乌斯反演的做法,因为这道题据称是莫比乌斯反演的入门题,恰好昨天看了组合数学那么本书上莫比乌斯反演那一部分(毫无疑问一点没看懂),这道题就当是个开始吧

莫比乌斯反演存在两个公式

约数公式:

F(n)=d|nf(d)f(n)=d|nμ(d)F(nd) F ( n ) = ∑ d | n f ( d ) ⟶ f ( n ) = ∑ d | n μ ( d ) F ( n d ) (d是n的约数)

倍数公式:

F(n)=n|df(d)f(n)=n|dμ(dn)F(d) F ( n ) = ∑ n | d f ( d ) ⟶ f ( n ) = ∑ n | d μ ( d n ) F ( d ) (d是n的倍数)

我们可以发现这两个公式中都有一个重要的共同的函数就是 μ() μ ( ) 函数

我们给出它的定义即性质:

hdu 1695 GCD(莫比乌斯反演经典入门||容斥原理+欧拉函数)_第1张图片

那么这时回到原来的题目上来,根据方法1的分析,我们知道将b,d进行除k后,等价于求满足 gcd(x,y)=1 g c d ( x , y ) = 1 的个数

直接求解上面的问题比较困难,但是如果我们求满足 gcd(x,y)=1 g c d ( x , y ) = 1 的 倍 数 就会非常简单

下面我们设 f(d) f ( d ) 表示满足 gcd(x,y)=d g c d ( x , y ) = d 的个数
F(d) F ( d ) 表示满足 gcd(x,y)=d g c d ( x , y ) = d 的 倍 数 的 个 数

对于 F(x) F ( x ) 这个函数是好求的

因为对于区间 [1,n] [ 1 , n ] 来说,能够被x或者x的倍数整除的数(即含有x或者x的倍数作为因子)的个数为
nx n x ,同理对于区间 [1,m] [ 1 , m ] 来说能被x或者x的倍数整除的数的个数为 mx m x

F(x)=nxmx ∴ F ( x ) = n x ⋅ m x

那么对于这道题目来说 n=bk,m=dk n = b k , m = d k

到此我们发现我们设的两个函数 F(x),f(x) F ( x ) , f ( x ) 满足莫比乌斯反演,并且满足的是倍数公式:

F(n)=n|df(d)f(n)=n|dμ(dn)F(d) F ( n ) = ∑ n | d f ( d ) ⟶ f ( n ) = ∑ n | d μ ( d n ) F ( d )

n=1,d n = 1 , d 便是1的倍数

所以我们要求:

f(1)=1|dμ(d1)F(d) f ( 1 ) = ∑ 1 | d μ ( d 1 ) F ( d )

=μ(1)F(1)+μ(2)F(2)++μ(min(m,n))F(min(m,n)) = μ ( 1 ) ⋅ F ( 1 ) + μ ( 2 ) ⋅ F ( 2 ) + ⋯ + μ ( m i n ( m , n ) ) ⋅ F ( m i n ( m , n ) )

直接for循环枚举 1min(bk,dk) 1 − m i n ( b k , d k ) 即可

但是很明显会有重复的现象

而重复的原因是出现了类似这种在重叠区间中求解就会出现重复的重复了两次我们需要减掉一次

hdu 1695 GCD(莫比乌斯反演经典入门||容斥原理+欧拉函数)_第2张图片

因此我们可以在重复区间即公共区间(因为都是从1开始,那么公共区间就是上届小的区间)再利用莫比乌斯反演求一遍,此时仍然是求了两边的值,我们只需要除2,然后用上面的答案减去就行了

普通筛选法:

void Init(){
    mu[1]=1;
    for(int i=1;i<=n;i++){
        for(int j=2*i;j<=n;j+=i){
            mu[j]-=mu[i];
        }
    }
}

下面是线性筛选莫比乌斯函数的模板 时间复杂度O(n)

    void Init(){
        int N=maxn;
        memset(prime,0,sizeof(prime));
        memset(mu,0,sizeof(mu));
        memset(vis,0,sizeof(vis));
        mu[1] = 1;
        cnt = 0;
        for(int i=2; iif(!vis[i]){
                prime[cnt++] = i;
                mu[i] = -1;
            }
            for(int j=0; j1;
                if(i%prime[j]) mu[i*prime[j]] = -mu[i];
                else{
                    mu[i*prime[j]] = 0;
                    break;
                }
            }
        }
    }

题解code:

#include 
using namespace std;
typedef long long ll;
const int maxn = 1e5+7;
bool vis[maxn];
int prime[maxn],mu[maxn];
int cnt;
void init(){
    memset(prime,0,sizeof(prime));
    memset(mu,0,sizeof(mu));
    memset(vis,0,sizeof(vis));
    mu[1] = 1;
    cnt = 0;
    for(int i = 2; i < maxn; i++){
        if(!vis[i]){
            prime[cnt++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < cnt && i * prime[j] < maxn; j++){
            vis[i*prime[j]] = 1;
            if(i % prime[j]) mu[i*prime[j]] = -mu[i];
            else{
                mu[i*prime[j]] = 0;
                break;
            }
        }
    }
}
int main(){
    init();
    int a,b,c,d,k;
    int t,cas = 0;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        if(k == 0 || k > b || k > d){
            printf("Case %d: 0\n",++cas);
            continue;
        }
        b /= k;
        d /= k;
        ll ans1 = 0,ans2 = 0;
        for(int i = 1; i <= min(b,d); i++){
            ans1 += (ll)mu[i] * (b / i) * (d / i);
        }
        for(int i = 1; i <= min(b,d); i++){
            ans2 += (ll)mu[i] * (min(b,d) / i) * (min(b,d) / i);
        }
        printf("Case %d: %lld\n",++cas,ans1-ans2/2);
    }
    return 0;
}

你可能感兴趣的:(组合数学,数论)