Codeforcs 75C Modified GCD 题解

本文介绍了一个编程问题的解决方案,该问题要求在给定区间[l, r]内找到a, b的最大公因数。通过利用数学定理,可以先计算gcd(a, b),然后对gcd的因数进行排序,对每个询问使用二分查找来优化效率。这种方法避免了不必要的枚举,确保了空间和时间复杂度的合理性。" 42419479,4847039,K-means聚类算法详解与应用,"['机器学习', '数据挖掘', '无监督学习', '聚类算法', 'K-means']

题意简述

给定 a , b ( a , b &lt; = 1 e 9 ) a,b(a,b&lt;=1e9) a,b(a,b<=1e9),和 T ( T &lt; = 1 e 4 ) T(T&lt;=1e4) T(T<=1e4)个询问,每次询问包含两个数 l , r ( l &lt; = r &lt; = 1 e 9 ) l,r(l&lt;=r&lt;=1e9) l,r(l<=r<=1e9),求 [ l , r ] [l,r] [l,r]中最大的 a , b a,b a,b的公因数。如果 l , r l,r l,r中没有一个数是 a , b a,b a,b的公因数,输出 − 1 -1 1

数据

输入

第一行给定两个正整数 a , b a,b a,b
第二行一个正整数 T T T
接下来 T T T行,每行两个正整数,表示一个询问 l , r l,r l,r

输出

对于每个询问,输出这个询问的答案。

样例

输入
9 27
3
1 5
10 11
9 11
输出
3
-1
9

解释

对于第一个询问,输出 3 3 3。当然, 9 9 9 27 27 27的公因数还有 9 9 9,但是 9 9 9不在给定的区间内。
对于第二个询问,区间中包含两个数 10 , 11 10,11 10,11,均不是 9 , 27 9,27 9,27的公因数。
对于第三个询问,区间包含三个数 9 , 10 , 11 9,10,11 9,10,11,其中满足是公因数且最大的是 9 9 9

思路

刚看到这个题,我们可能有这样的思路:
枚举区间,判断是否是 a , b a,b a,b的公因数,找最大的那个。

但是,我们会发现,我们珂能多枚举了很多!因为区间中珂能有很多数不是 a , b a,b a,b的公因数。要优化掉这些多余的枚举,就要 开O2 想办法。一会,我们可能会想到,我们珂不珂以枚举 a , b a,b a,b的公因数,然后判断是否在区间内,求最大呢?

此时我们联想到一个定理: a , b a,b a,b的所有公因数都是 g c d ( a , b ) gcd(a,b) gcd(a,b)的因数。
还有另外一个定理:一个数 n n n的因数关于 n \sqrt{n} n 对称(即两两乘积相等)。
根据这两个定理,我们就珂以把 g c d ( a , b ) gcd(a,b) gcd(a,b)先求出来,然后打个表,排一下序,对于每次询问,二分一下就珂以了。那么,打表(也就是预处理)会不会爆空间呢?由第二个定理可知, g c d ( a , b ) gcd(a,b) gcd(a,b)的因数个数不会超过 2 g c d ( a , b ) 2\sqrt{gcd(a,b)} 2gcd(a,b) 。而 g c d ( a , b ) gcd(a,b) gcd(a,b)有不会超过 m i n ( a , b ) min(a,b) min(a,b),所以空间战友不会超过 1 e 9 \sqrt{1e9} 1e9 ,一定能存下。

具体如何二分呢?只要 u p p e r _ b o u n d upper\_bound upper_bound一下即可。 u p p e r _ b o u n d ( l , r , x ) upper\_bound(l,r,x) upper_bound(l,r,x)会返回指针区间 [ l , r ) [l,r) [l,r)内第一个大于 x x x的位置。那么,我们把这个位置 − 1 -1 1就是最后一个 &lt; = x &lt;=x <=x的位置了。当然,我们在减去数组头,就珂以得到这个是数组的第几个了。

代码

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    #define N 100100
    int a,b;
    int d[N];//d保存gcd(a,b)的因数

    void Build()
    {
        int g=__gcd(a,b);//求出gcd(a,b)
        int& cnt=d[0];//为了节省空间,用d的第一个位置作为计数器
        for(int i=1;i*i<=g;++i)//枚举到根号即珂
        {
            if (g%i==0)
            {
                d[++cnt]=i;
                if (i*i!=g)//避免重复枚举
                {
                    d[++cnt]=g/i;
                }
            }
        }
        sort(d+1,d+cnt+1);//排序,方便二分
    }

    void Query()
    {
        int T;scanf("%lld",&T);
        while(T--)
        {
            int l,r;
            scanf("%lld%lld",&l,&r);
            int k=(upper_bound(d+1,d+d[0]+1,r)-1)-d;
            //上面说过这里
            if (d[k]>=l)//还要判一下是否>l
            {
                printf("%lld\n",d[k]);
            }
            else
            {
                puts("-1");
            }
        }
    }

    void Main()
    {
        scanf("%lld%lld",&a,&b);
        Build();
        Query();
    }
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总笔记界面

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值