Mophues(莫比乌斯反演+分段)

Mophues

 As we know, any positive integer C ( C >= 2 ) can be written as the multiply of some prime numbers:
    C = p1×p2× p3× ... × pk
which p1, p2 ... pk are all prime numbers.For example, if C = 24, then:
    24 = 2 × 2 × 2 × 3
    here, p1 = p2 = p3 = 2, p4 = 3, k = 4

Given two integers P and C. if k<=P( k is the number of C's prime factors), we call C a lucky number of P.

Now, XXX needs to count the number of pairs (a, b), which 1<=a<=n , 1<=b<=m, and gcd(a,b) is a lucky number of a given P ( "gcd" means "greatest common divisor").

Please note that we define 1 as lucky number of any non-negative integers because 1 has no prime factor. 

Input
The first line of input is an integer Q meaning that there are Q test cases.
Then Q lines follow, each line is a test case and each test case contains three non-negative numbers: n, m and P (n, m, P <= 5×10 5. Q <=5000).
Output
For each test case, print the number of pairs (a, b), which 1<=a<=n , 1<=b<=m, and gcd(a,b) is a lucky number of P.
Sample Input

2
10 10 0
10 10 1

Sample Output

63
93

题意:

1x,yn 1 ≤ x , y ≤ n ,求 gcd(x,y) g c d ( x , y ) 分解后质因数个数小于等于k的 (x,y) ( x , y ) 的对数

分析:

这道题依然是莫比乌斯反演的套路,但是说实话,却是要比其他类型的难理解,也难以想到


首先我们先回顾一下,目前已经做到的过的莫比乌斯反演题目的问法和解法

1)求 x[1,n],y[1,m] x ∈ [ 1 , n ] , y ∈ [ 1 , m ] gcd(x,y)=1 g c d ( x , y ) = 1 的 数 对 个 数

直接套用莫比乌斯反演公式

f(1)=1|dμ(d)F(d)=1|dμ(d)ndmd f ( 1 ) = ∑ 1 | d μ ( d ) F ( d ) = ∑ 1 | d μ ( d ) n d ⋅ m d

2)求 x[1,n],y[1,m] x ∈ [ 1 , n ] , y ∈ [ 1 , m ] gcd(x,y)=k g c d ( x , y ) = k 的 数 对 个 数

转换成求 gcd(xk,yk)=1 g c d ( x k , y k ) = 1 的数对个数,问题变成类型 1)

3)求 x[1,n],y[1,m] x ∈ [ 1 , n ] , y ∈ [ 1 , m ] gcd(x,y)= g c d ( x , y ) = 素 数 的 数 对 个 数

这个问题稍微复杂些

实际上求 gcd(x,y)=p g c d ( x , y ) = p 的数对个数仍然可以转化成求 gcd(xp,yp)=1 g c d ( x p , y p ) = 1 的数对个数

只不过外层还需要套一层for循环来枚举素数

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

但是这样的时间复杂度比较高

因此通过化简我们令 t=pd t = p d

得到

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

这样可以设 a[t]=p|tμ(tp) a [ t ] = ∑ p | t μ ( t p ) ,完全可以进行预处理,在根据 ntmt n t ⋅ m t 向下取整的特性,完全可以分段,因而进一步优化,此类型的例题可查看Primes in GCD Table SPOJ - PGCD(莫比乌斯反演+分段)


以上三种类型是我目前学习莫比乌斯反演做到过的三种类型的题目,而今天这道题目,和类型3)的解法有很大的相似之处,下面详细解释

仍然是相同的套路设

f(d):gcd(x,y)=d f ( d ) : 满 足 g c d ( x , y ) = d 的 数 对 的 个 数

F(d):gcd(x,y)=d F ( d ) : 满 足 g c d ( x , y ) = d 及 其 倍 数 的 数 对 的 个 数

显然有 F(x)=nxmx F ( x ) = ⌊ n x ⌋ ⋅ ⌊ m x ⌋

反演后得到

f(n)=n|dμ(dn)nnmn f ( n ) = ∑ n | d μ ( d n ) ⋅ ⌊ n n ⌋ ⋅ ⌊ m n ⌋
(后面都是整数除法向下取整,省略不再写向下取整号)

如果我们枚举最大公因数 g g (注意类比题目类型3),枚举素数 p p

我们同样得到

ans=gmin(m,n)g|dμ(d)ngdmgd a n s = ∑ g m i n ( m , n ) ∑ g | d μ ( d ) ⋅ n g d ⋅ m g d

继续类比设 t=gd,d=tg t = g ⋅ d , 则 d = t g

ans=t=1min(n,m)ntmtg|tμ(tg) a n s = ∑ t = 1 m i n ( n , m ) n t ⋅ m t ∑ g | t μ ( t g )

同样可以预处理出 g|tμ(tg) ∑ g | t μ ( t g ) ,并且由于向下取整也可以分段来进行优化。


好!到目前为之,我相信大家一定会有一个疑问,上面推导分析的这一堆和题目有什么关系???

题目的条件是最大公因数 gk g 的 素 因 子 的 个 数 小 于 等 于 k

仔细观察上面的推导我们发现实际上问题已经解决一半了,因为上面的公式我们可以求解最大公因数为g的所有数对的个数,只是没有g的素因子个数这一条件

这是就得靠预处理来发挥作用了。

题目中有个地方是需要进行预处理的,即我们需要预处理 g|tμ(tg) ∑ g | t μ ( t g )

对于求 gcd= g c d = 素 数 的 那 道 题 目 ,我们只需要设 a[t]=g|tμ(tg) a [ t ] = ∑ g | t μ ( t g ) 预处理出 a[t] a [ t ]

而这道题多了一个条件我们只需要多开一维数组就可以完美解决这个问题,这也是这道题的解题关键,也是这道题最难想到的地方

首先我们在预处理出mu函数的时候会用到素数,在这个过程中完全可以顺带预处理出每个数字的素因子个数,用 cnt[] c n t [ ] 记录

1) 先设 f[i][j]didj f [ i ] [ j ] : 代 表 最 大 公 因 数 d 的 倍 数 i , 且 d 的 素 因 子 个 数 为 j 的 数 对 个 数

我们可先预处理出 f[i][j] f [ i ] [ j ] ,即预处理出 g|tμ(tg)=f[t][cnt[g]] ∑ g | t μ ( t g ) = f [ t ] [ c n t [ g ] ]

然后因为题目要求最大公因数的素因子小于等于k的都可以

2) 那么我们第二步来求 f[i][j]j f [ i ] [ j ] 中 j 的 前 缀 和 ,即一开始我们求的是倍数i,对应的不同的因数的素因子个数j(同一个i可以有很多不同的g可以整除i,因此j也不同)

所以现在我们想要求 f[i][j]ijdj f [ i ] [ j ] : 代 表 i 的 含 义 不 变 j 变 成 最 大 公 因 数 d 的 素 因 子 个 数 小 于 等 于 j 的 个 数

所以我们只需要固定i,求j的前缀和即可,两重for循环即可搞定: f[i][j]+=f[i][j1] f [ i ] [ j ] + = f [ i ] [ j − 1 ]

3) 最后因为需要分段,所以我们需要在预处理出 i i 的前缀和,即j不需要变,两重for循环: f[i][j]+=f[i1][j] f [ i ] [ j ] + = f [ i − 1 ] [ j ] 即可

4) 预处理结束后,开始进行真正计算了,首先求出每一分段的范围,然后直接前缀和插查询即可

例如范围是i-j,这一段内 ntmt n t ⋅ m t 值相同,那么这一段的答案为

njmj(f[j][k]f[i1][k]) n j ⋅ m j ⋅ ( f [ j ] [ k ] − f [ i − 1 ] [ k ] )

分段思想这里不再过多讲解,可以看一下上面求gcd=素数的题目连接里面做了解释

code:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
int tot = 0;
int mu[maxn],prime[maxn],f[maxn][20+5];
int cnt[maxn];
bool vis[maxn];
void init(){
    mu[1] = 1;
    tot = 0;
    //预处理mu函数
    for(int i = 2; i < maxn; i++){
        if(!vis[i]){
            prime[tot++] = i;
            mu[i] = -1;
            cnt[i] = 1;
        }
        for(int j = 0; j < tot && i * prime[j] < maxn; j++){
            vis[i*prime[j]] = true;
            cnt[i*prime[j]] = cnt[i] + 1;//记录下每个数素因子个数(可以含相同的素因子)
            if(i % prime[j]){
                mu[i*prime[j]] = -mu[i];
            }
            else{
                mu[i*prime[j]] = 0;
                break;
            }
        }
    }
    for(int i = 1; i < maxn; i++){
        for(int j = i; j < maxn; j += i){
            f[j][cnt[i]] += mu[j/i];//预处理处f[i][j],f[i][j]记录最大公因数d的倍数i且d的素因子个数为j的数对个数
        }
    }
    for(int i = 1; i < maxn; i++){
        for(int j = 1; j < 20; j++){
            f[i][j] += f[i][j-1];//预处理倍数相同情况下,公因子d的素因子不同个数的数对个数前缀和
                                 //即f[i][j]变成倍数为i,公因子d的素因子有小于等于j个的数对个数前缀和
        }
    }
    for(int i = 1; i < maxn; i++){
        for(int j = 0; j < 20; j++){
            f[i][j] += f[i-1][j];//预处理倍数i的前缀和,用于分段
        }
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    init();
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m,k;
        scanf("%d%d%d",&n,&m,&k);
        ll ans = 0;
        k = min(k,19);
        int j;
        if(n > m) swap(n,m);
        for(int i = 1; i <= n; i = j + 1){
            j = min(n / (n / i), m / (m / i));
            ans += (ll)(n / j) * (m / j) * (f[j][k] - f[i-1][k]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

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