Hankson的趣味题(暴搜、数论)

博客探讨了Hankson的趣味题的解题思路,从最初的暴力搜索算法(时间复杂度O(n*b1*log b1))到利用质因数分解优化为O(n*b1/log b1),再到最终通过数学逻辑简化为O(n*b1)。文章介绍了如何利用唯一分解定理和质数定理来优化解题过程。
原题题面:Hankson的趣味题
思路:

在没想到纯数学逻辑前,容易想到一个朴素算法:用试除法求出 b1 的所有约数,之后逐一检验是否满足条件,该暴搜的时间复杂度大概是O(n* b 1 \sqrt b1 b 1 * log b1)。实际测试会有一个测试数据过不了。
那么就是优化问题了。
由唯一分解定理可知:如果 x 是 b1 的约数,则x所有的质因数都是b1的质因数。
由此我们可以先预先处理出 1 ~ b 1 \sqrt b1 b 1 的所有质数,然后用这些质数去试除 b1,得到相应的质因子及其数量。最后用DFS枚举出每一个b1的约数,并将其存储到数组中,再进行判断条件。
质数定理:1 ~ n 中的质数个数约为 n / l n ( n ) n/ln(n) n/ln(n)
由质数定理可知,可以在 ( b   i   ) \sqrt (b~i~) ( b i ) / log(bi) 时间复杂度内将 b1 的质因数都分解出来。所以总的时间复杂度为O(n* ( b 1 ) \sqrt(b1) ( b1) / log(b1))。这样就可以过了。

纯数学逻辑来做,我们可以推出当 x 与 a0 互质时
gcd( x / a 1 x/a1 x/a1, a 0 / a 1 a0/a1 a0/a1) ==1
gcd( b 1 / x b1/x b1/x, b 0 / b 1 b0/b1 b0/b1) ==1
所以我们就可以直接枚举1~ ( b 1 ) \sqrt (b1) ( b1) 内的所有b1的约数x,然后将其拉去判断上面两个条件就行了。
注意:这样会少统计了大于 ( b 1 ) \sqrt(b1) ( b1)的约数,所以应写一个data存储 b1/x,也将data拿去判断。
时间复杂度约为O(n * ( b 1 ) \sqrt(b1) ( b1))。

代码如下:
  • 暴搜
#include <iostream>
#include <algorithm>
 
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
//预先算一下sqrt(2e9),易知其小于45000
//易知,2e9中不可能有多余10个不同的质因子,开50宽松一点。
const int N = 45000, M = 50;

int n;
int primes[N], cnt;
bool st[N];
 
PII factor[M];
int cnt_f;
int divider[N], cnt_d;
 
void get_primes(int x)
{
    for(int i = 2; i <= x; i++)
    {
        if(!st[i])    primes[cnt ++] = i;
        for(int j = 0; primes[j] <= x / i; i++)
        {
            st[i * primes[j]] = true;
            if(i % primes[j] == 0)    break;
        }
    }
}
 
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
 
void dfs(int u, int p)
{
    if(u > cnt_f)
    {
        divider[cnt_d ++] = p;
        return ;
    }
    for(int i = 0; i <= factor[u].second; i++)
    {
        dfs(u + 1, p);
        p *= factor[u].first;
    }
}
 
int main()
{
    get_primes(N);
    cin >> n;
    while(n--)
    {
        int a0, a1, b0, b1;
        cin >> a0 >> a1 >> b0 >> b1;
         
        int t = b1;
        cnt_f = 0;
        for(int i = 0; i < cnt && primes[i] <= t / primes[i]; i++)
        {
            if(t % primes[i] == 0)
            {
                int s = 0;
                while(t % primes[i] == 0)
                {
                    t /= primes[i];
                    s ++;
                }
                factor[++ cnt_f] = {primes[i], s};
            }
        }
        if(t > 1)    factor[++ cnt_f] = {t, 1};
         
        cnt_d = 0;
        dfs(1, 1);
         
        int ans = 0;
        for(int i = 0; i < cnt_d; i++)
        {
            int x = divider[i];
            if(gcd(x, a0) == a1 && (LL)x * b0 / gcd(x, b0) == b1)
                ans ++;
        }
        cout << ans << endl;
    }
    return 0;
}
  • 数学思路
#include <iostream>
#include <algorithm>
 
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
 
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
 
int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int a0, a1, b0, b1;
        cin >> a0 >> a1 >> b0 >> b1;
        int ans = 0;
        for(int x = 1; x <= b1 / x; x++)
        {
            if(b1 % x == 0)
            {
                if(x % a1 == 0 && gcd(x / a1, a0 / a1) == 1 && gcd(b1 / b0, b1 / x) == 1)    ans ++;
                int y = b1 / x;
                if(x == y)    continue;
                if(y % a1 == 0 && gcd(y / a1, a0 / a1) == 1 && gcd(b1 / b0, b1 / y) == 1)    ans ++;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

非常感谢您能看到这里~~~有问题可以私信我哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值