原题链接:200. Hankson的趣味题 - AcWing题库

题意:给定 a0, a1, b0, b1 ,找到 x 使得 x与a0最大公约数是 a1,与b0最小公倍数是b1,统计这样的 x 的数量

思路

x 与 a0 的公约数 是 a1 ,与b0的公倍数的 b1

考虑直接遍历,b1的约数大概1600个,找约数大概要 根号 的复杂度,相乘之下可能会超时,且有多次询问

优化找约数方法,先求质数,考虑到约数就是质因子的不同幂次乘积,可以先对最大的范围预处理找质数,对每个询问试除法找质因数,再用dfs计算不同的幂次组合相乘的结果(即约数),就可以找到所有约数

时间复杂度:

有质数定理:

1~n 中质数个数约为

可以求出预处理范围,同时时间复杂度为:

  • 线性筛求质数 ,时间为质数个数
  • dfs求约数,时间为约数个数 1600
  • n组数据 2000

dfs与求质数在时间上平行 即最终时间复杂度约为

实现

#include <iostream>
#include <algorithm>
#include <cstring>
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long LL;
 
 
//凭经验给出约数,质数最大个数和质因数最大个数
const int N = 45000, M = 50;
 
int p[N];
PII factor[M];
 
int cntf; //记录质因子是
int divider[N], cntd;
 
int n;
bool st[N];
 
 
void get_prime(int n)
{
    int cnt = 0;
    for(int i = 2; i <= n; i ++)
    {
        if(!st[i]) p[cnt ++] = i;
        for(int j = 0; p[j] <= n / i; j ++)
        {
           st[p[j] * i] = true;
           if(i % p[j] == 0) break;
        }
    }
}
 
//对素数试除法
void get_factor(int n)
{
    cntf = 0;
    for(int i = 0; p[i] <= n / p[i]; i ++)
    {
        int pr = p[i];
        //如果是质因子,求其幂范围
        if(n % pr == 0)
        {
            int s = 0;
            while(n % pr == 0) s ++, n /= pr;
            factor[++ cntf] = {pr, s};
        }
    }
    //如果剩下的这个也是约数
    if(n > 1) factor[++ cntf] = {n, 1};
}
 
 
//对素数所有可能幂数组合的取值进行搜索
void dfs(int u, int p)
{
    //如果u已经遍历过了所有质数
    if(u > cntf)
    {
        divider[cntd ++] = p;
        return;
    }
    
    for(int i = 0; i <= factor[u].second; i ++)
    {
        //对每个素数的不同幂次组合
        dfs(u + 1, p);
        p *= factor[u].first; 
    }
}
 
 
 
int gcd(int a, int b)
{
    return b?gcd(b, a % b):a;
}
 
 
int main()
{
    cin >> n;
    
    get_prime(N);
    
    while(n --)
    {
        int a0, a1, b0, b1;
        cin >> a0 >> a1 >> b0 >> b1;
        
        get_factor(b1);
        
        cntd = 0;
        dfs(1, 1);
        
        int cnt = 0;
        for(int i = 0; i < cntd; i ++)
        {
            LL x = divider[i];
            if(gcd(x, a0) == a1 && (LL)x * b0 / gcd(x, b0) == b1)
                cnt ++;
        }
        
        cout << cnt << endl;
    }
    
    return 0;
}

注意此题提供一种新的约数求法

同时利用查询前预处理可以降低时间复杂度