POJ 2429 分解质因数

https://vjudge.net/problem/POJ-2429
Given two positive integers a and b, we can easily calculate the greatest common divisor (GCD) and the least common multiple (LCM) of a and b. But what about the inverse? That is: given GCD and LCM, finding a and b.
Input
The input contains multiple test cases, each of which contains two positive integers, the GCD and the LCM. You can assume that these two numbers are both less than 2^63.
Output
For each test case, output a and b in ascending order. If there are multiple solutions, output the pair with smallest a + b.
Sample Input
3 60
Sample Output
12 15

题意:给出两个数的gcd,lcm的值,求两个数分别是多少?有多解时输出两数和最小的一组。

分析:
我们可以根据lcm和gcd,得到a*b/gcd=lcm;
也就是说,lcm/gcd就是a和b中互斥的因子和了
,那么我们设lcm/gcd=X
;因此可以枚举出X是由那两个数相乘得出的;

但是,有一个限制条件,就是所求得的答案a与b,相加的和应该最小,那这该怎么枚举呢,是从1枚举还是从sqrt(X)开始枚举呢???

这就需要一点点的数学方法去构造一个函数,我们设枚举的数是 i ,那么

⇒ a=i*gcd,b=lcm/i*gcd;

⇒ a+b=gcd*(i+lcm/i);

⇒ 令f(x)=x+A/x, 1<=x<=sqrt(A);

对f(x)求导,的f(x)’=1-A/x2

因此可以得出f(x)在定义域上是单调递减的,因此x越大,和越小,即枚举应当从最大值开始;

因此我们可以得到下面的代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <bitset>
#include <set>
#include <utility>
#include <sstream>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define inf 0x3f3f3f3f
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define lep(i,l,r) for(int i=l;i>=r;i--)
#define ms(arr) memset(arr,0,sizeof(arr))
//priority_queue<int,vector<int> ,greater<int> >q;
const int N = 1e5 + 10;
const int mod = 1e9+7;
ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}
int main()
{
//    #ifndef ONLINE_JUDGE
//    freopen("in.txt","r",stdin);
//    #endif // ONLINE_JUDGE
    ll n,m;
    while(scanf("%lld%lld",&n,&m)!=EOF){
        ll res=m/n;
        ll ub=sqrt(res);
        for(ll i=ub;i>=1;i--){
            if(res%i==0 && gcd(i,res/i)==1){
                printf("%lld %lld\n",i*n,m/i);
                break;
            }
        }
    }
    return 0;
}

很不幸,反手就是一个TTTTTTTTT;
超时了!!!
(Java可以,&……%&%*&%……&¥……%¥%R&%&

其实,大体上的思路已经出来了,让我们T的就是,找最大因子太耗时了,因此,我们C的选手需要另辟蹊径了;
小伙伴们如果知道有个东东叫做miller_robin算法和pollard分解质因数

这个东东可以很快就能够分解出大数的质因子,在这我就不赘述了,大家可以去找找有关的文章(不想理解的可以直接套模板呀,真香!!!);

有了这个利器,我们剩下的就是用一个DFS或者其他的方法枚举找到这个最大值

【注】用G++交可能会RE呀,可以用C++交

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <bitset>
#include <ctime>
#include <set>
#include <utility>
#include <sstream>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define inf 0x3f3f3f3f
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define lep(i,l,r) for(int i=l;i>=r;i--)
#define ms(arr) memset(arr,0,sizeof(arr))
//priority_queue<int,vector<int> ,greater<int> >q;
const int N = 1e5 + 10;
const int mod = 1e9+7;
vector<ll> factor;
vector<ll> prime_factor;
const int s = 20;

ll multi_mod(ll a, ll b, ll n)
{
    a %= n;
    b %= n;
    ll res = 0;
    while(b)
    {
        if(b & 1)
        {
            res += a;
            if(res >= n) res -= n;
        }
        a <<= 1;
        if(a >= n) a -= n;
        b >>= 1;
    }
    return res;
}

ll pow_mod(ll x, ll n, ll mod)
{
    if(n == 1)  return x % mod;
    x %= mod;

    ll tmp = x;
    ll res = 1;
    while(n > 0)
    {
        if(n & 1)   res = multi_mod(res, tmp, mod);
        tmp = multi_mod(tmp, tmp, mod);
        n >>= 1;
    }
    return res;
}

bool witness(ll a, ll n, ll u, ll t)
{
    ll res = pow_mod(a, u, n);
    ll last = res;
    for(int i = 0; i < t; i++)
    {
        res = multi_mod(res, res, n);
        if(res == 1 && last != 1 && last != n-1)    return true;
        last = res;
    }
    if(res != 1)    return true;
    else        return false;
}

bool robin_miller(ll n) //判断是否素数
{
    if(n < 2)    return false;
    if(n == 2)  return true;
    if(!(n & 1))    return false;

    ll u = n - 1, t = 0;
    while(!(u & 1)) u >>= 1, t++;
    if(t >= 1 && (u & 1) == 1)
    {
        for(int i = 0; i < s; i++)
        {
            ll a = rand() % (n-1) + 1;
            if(witness(a, n, u, t)) return false;   //不是素数
        }
    }
    return true;    //是素数
}

ll gcd(ll a, ll b)
{
    if(a == 0)  return 1;
    if(a < 0)    return gcd(-a, b);
    while(b)
    {
        ll t = a % b;
        a = b;
        b = t;
    }
    return a;
}

ll pollard_rho(ll x, ll c)
{
    ll i = 1, x0 = rand() % x;
    ll y = x0;
    ll k = 2;
    for(;;)
    {
        i++;
        x0 = (multi_mod(x0, x0, x) + c) % x;
        ll d = gcd(y - x0, x);  //这里传给gcd的参数可能负数,注意分别讨论
        if(d != 1 && d != x) return d;  //这里表明找到了一个因子
        if(y == x0) return x;
        if(i == k)
        {
            y = x0;
            k += k;
        }
    }
}

void find_fac(ll n)
{
    if(n == 1)    return;
    if(robin_miller(n))
    {
        prime_factor.push_back(n);
        return;
    }
    ll p = n;
    while(p >= n) p = pollard_rho(p, rand() % (n-1) + 1);
    find_fac(p);
    find_fac(n / p);
}

ll DFS(int id,int len,ll res,ll m){

    if(res>m) return 0;
    if(id==len || res==m) return res;

    ll t1=DFS(id+1,len,res,m);

    if(res*factor[id] > m) return t1;
    else{
        ll t2=DFS(id+1,len,res*factor[id],m);
        return max(t1,t2);
    }
}



int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    ll n,m;
    while(scanf("%lld%lld",&n,&m)!=EOF){
        srand(time(NULL));
        prime_factor.clear();
        factor.clear();
        m/=n;
        find_fac(m);
        ll tem=m;
        for(int i=0;i<prime_factor.size();i++){
            ll res=1;
            //相同因子的构成一个整体,这样就保证了后续DFS枚举的因子都是互斥的
            while(tem%prime_factor[i]==0 && tem)
                res*=prime_factor[i],tem/=prime_factor[i];
            if(res!=1) factor.push_back(res);
        }
        tem=(ll)sqrt(m*1.0);
        ll base1=DFS(0,factor.size(),1,tem);
        ll base2=m/base1;
        printf("%lld %lld\n",base1*n,base2*n);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值