Primes in GCD Table SPOJ - PGCD(莫比乌斯反演+分段)

文章详细介绍了如何计算给定大小的GCD表中素数的出现次数,通过莫比乌斯反演公式进行优化,解决了一个有趣的数论问题。

Primes in GCD Table SPOJ - PGCD

Johnny has created a table which encodes the results of some operation -- a function of two arguments. But instead of a boring multiplication table of the sort you learn by heart at prep-school, he has created a GCD (greatest common divisor) table! So he now has a table (of height a and width b), indexed from (1,1) to (a,b), and with the value of field (i,j) equal to gcd(i,j). He wants to know how many times he has used prime numbers when writing the table.
Input

First, t ≤ 10, the number of test cases. Each test case consists of two integers, 1 ≤ a,b < 107.
Output

For each test case write one number - the number of prime numbers Johnny wrote in that test case.
Example

Input:
2
10 10
100 100

Output:
30
2791
题意:

给出一个数 N,M1xN,1yMgcd(x,y)x,y N , M , 求 1 ≤ x ≤ N , 1 ≤ y ≤ M , 且 g c d ( x , y ) 为 素 数 的 数 对 x , y 的数量。

分析:

假设 p p 为素数,那么这道题就转化成了求

gcd(x,y)=p
的数对有多少个。

这样就和我们之前做过的题求 gcd(x,y)=k g c d ( x , y ) = k 的数对有多少个是一样的了


回想以下之前的求解步骤

1)要求 gcd(x,y)=k g c d ( x , y ) = k 的数对多少个相当于求 gcd(xk,yk)=1 g c d ( x k , y k ) = 1 的数对有多少个

2)利用莫比乌斯反演:
f(d)gcd(x,y)=d f ( d ) 表 示 g c d ( x , y ) = d 的 个 数

F(d)gcd(x,y)=dd F ( d ) 表 示 g c d ( x , y ) = d 以 及 d 的 倍 数 的 个 数

那么根据莫比乌斯反演公式

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

而且我们易求 F(d)=ndmd F ( d ) = n d ⋅ m d

所以答案实际上是求

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

此时的 F(d)=nkdmkd F ( d ) = n k d ⋅ m k d


对于这道题目,求 gcd(x,y)=p g c d ( x , y ) = p 的 个 数 是不是就是和上面解法一样了,求 gcd(xp,yp)=1 g c d ( x p , y p ) = 1 的个数,只不过p是不知道的,需要我们枚举素数p

因此我们得到公式

ans=pmin(n,m)1|dmin(n,m)μ(d1)F(d) a n s = ∑ p m i n ( n , m ) ∑ 1 | d m i n ( n , m ) μ ( d 1 ) F ( d )

=pmin(n,m)1|dmin(n,m)μ(d)npdmpd = ∑ p m i n ( n , m ) ∑ 1 | d m i n ( n , m ) μ ( d ) n p d ⋅ m p d

pmin(n,m)1|dmin(n,m)μ(d)npdmpd ∑ p m i n ( n , m ) ∑ 1 | d m i n ( n , m ) μ ( d ) n p d ⋅ m p d

但是直接这样枚举的话会超时,那么我们需要考虑优化

t=pdd=tp t = p d , 即 d = t p

通过变量代换得到

ans=t=1min(m,n)p|tμ(tp)ntmt a n s = ∑ t = 1 m i n ( m , n ) ∑ p | t μ ( t p ) n t ⋅ m t

=t=1min(m,n)ntmtp|tμ(tp) = ∑ t = 1 m i n ( m , n ) n t ⋅ m t ∑ p | t μ ( t p )

a[t]=p|tμ(tp) a [ t ] = ∑ p | t μ ( t p )

=t=1min(m,n)ntmta[t] = ∑ t = 1 m i n ( m , n ) n t ⋅ m t a [ t ]

因此我们可以预处理出来 a[t] a [ t ]

同时我们还发现 ntmt n t ⋅ m t 因为是整数除法向下取整,因此中间必定有连续的一段(比如 t1,t,t+1,t+2 t − 1 , t , t + 1 , t + 2 )这是 ntmt n t ⋅ m t 的值是一样的只不过 a[t] a [ t ] 不同,所以我们可以进行分段,预处理出 a[t] a [ t ] 的前缀和,这样只要我们知道了这一段的范围,就能通过前缀和O(1)查询这一段的 a[t] a [ t ] 的和了然后直接乘上 ntmt n t ⋅ m t 的值,最后求和

那么这个每一段的范围怎么求呢?

我们可以求出 d1=nt,d2=mt d 1 = n t , d 2 = m t ,因此每个这一段的最大范围是 r1=nd1,r2=md2 r 1 = n d 1 , r 2 = m d 2 ,这样 min(r1,r2) m i n ( r 1 , r 2 ) 便是这一段的范围

证明可见下面这道题 Ice Rain(数论+分段)是分段思想的简单应用

code:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 5;
const int N = 1e6 + 10;
bool vis[maxn];
int prime[N],pnum,mu[maxn],a[maxn];
void init(int n){
    mu[1] = vis[1] = 1;
    pnum = 0;
    //预处理处莫比乌斯函数
    for(int i = 2; i <= n; i++){
        if(!vis[i]){
            prime[pnum++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < pnum; j++){
            if(i * prime[j] > n) break;
            vis[i*prime[j]] = 1;
            if(i % prime[j] == 0){
                mu[i*prime[j]] = 0;
                break;
            }
            mu[i*prime[j]] = -mu[i];
        }
    }
    //求a[t]与前缀和
    for(int i = 1; i <= n; i++){
        if(mu[i]){
            for(int j = 0; j < pnum; j++){
                if(i * prime[j] > n) break;
                a[i*prime[j]] += mu[i];
            }
        }
    }
    for(int i = 1; i <= n; i++) a[i] += a[i-1];
}
int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    init(10000000);
    int t,n,m;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        if(n > m) swap(n,m);
        ll ans = 0;
        for(int i = 1; i <= n; i++){
            int tmp1 = n / i,tmp2 = m / i;
            int next = min(n/tmp1,m/tmp2);
            ans += (ll)tmp1 * tmp2 * (a[next] - a[i-1]);
            i = next;
        }
        printf("%lld\n",ans);
    }   
    return 0;
}
莫比乌斯反演是一种在数论中非常重要的工具,尤其在组合数学与算法设计中有着广泛的应用。它主要处理的是两个函数之间的关系,这两个函数通过某种特定的方式相互定义。具体来说,如果有一个函数 $ F(n) $ 定义为另一个函数 $ f(d) $ 对所有 $ d $ 整除 $ n $ 的值的总和,即 $ F(n) = \sum_{d|n} f(d) $,那么可以通过莫比乌斯反演公式 $ f(n) = \sum_{d|n} \mu(d)F(\frac{n}{d}) $ 来恢复出 $ f(n) $,其中 $ \mu(d) $ 是莫比乌斯函数 [^1]。 ### 数学原理 莫比乌斯反演的核心在于莫比乌斯函数 $ \mu(n) $ 的定义,它是一个重要的数论函数,用于描述两个相邻整数之间的关系。莫比乌斯函数 $ \mu(n) $ 的定义如下: - $ \mu(n) = 1 $,如果 $ n $ 是一个平方自由的正整数,且具有偶数个不同的质因子。 - $ \mu(n) = -1 $,如果 $ n $ 是一个平方自由的正整数,且具有奇数个不同的质因子。 - $ \mu(n) = 0 $,如果 $ n $ 包含平方因子(即存在某个质数 $ p $ 使得 $ p^2 | n $)。 这里的“平方自由”意味着没有一个质数的平方能整除 $ n $。莫比乌斯函数的一个重要性质是它能够帮助我们反转某些类型的求和公式,这就是所谓的莫比乌斯反演定理 [^1]。 ### 在算法中的应用 在算法设计中,莫比乌斯反演主要用于优化那些涉及大量枚举和计算的问题。例如,在解决一些组合计数问题时,直接计算某些复杂度较高的函数可能非常困难,而通过莫比乌斯反演技术,可以将原问题转化为更容易处理的形式 [^3]。 一个典型的例子是计算不超过 $ n $ 的 square-free 数的个数,这类问题可以通过应用莫比乌斯反演的思想来解决。此外,莫比乌斯反演还经常应用于求解与数论相关的各种问题,如求解特定条件下数对的数量等 [^4]。 ### 示例代码 为了高效地应用莫比乌斯反演,通常需要预先计算出一定范围内的莫比乌斯函数值,这可以通过线性筛法实现。以下是一个用于计算莫比乌斯函数值的 C++ 函数示例: ```cpp #include <iostream> #include <vector> using namespace std; const int MAXN = 1e6 + 5; // 根据实际需求调整大小 vector<int> mu; // 莫比乌斯函数数组 vector<bool> vis; // 访问标记数组 vector<int> primes; // 存储素数 void get_mu(int n) { mu.resize(n + 1); vis.resize(n + 1, false); mu[1] = 1; for (int i = 2; i <= n; ++i) { if (!vis[i]) { primes.push_back(i); mu[i] = -1; } for (int j = 0; j < primes.size() && primes[j] * i <= n; ++j) { vis[primes[j] * i] = true; if (i % primes[j] == 0) { mu[i * primes[j]] = 0; break; } else { mu[i * primes[j]] = -mu[i]; } } } } int main() { int n = 100; // 示例计算前100个数的莫比乌斯函数值 get_mu(n); for (int i = 1; i <= n; ++i) { cout << "mu(" << i << ") = " << mu[i] << endl; } return 0; } ``` 这段代码实现了莫比乌斯函数的线性筛法计算,首先初始化了必要的数据结构,然后通过遍历所有可能的素数及其倍数来更新莫比乌斯函数的值。这种方法的时间复杂度接近线性,非常适合大规模数据的预处理 [^1]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值