裏紙

ほぼ競プロ、たまに日記

POJ 2429 - GCD & LCM Inverse

問題

2429 -- GCD & LCM Inverse

問題概要

GCD(a,b)の値と、LCM(a,b)の値が与えられるとき、a,bの値を答えよ。ただし候補が複数ある場合には、その中でもa+bが最小のものを答えよ。

  •  GCD(a,b), LCM(a,b) \lt 2^{63}

イデア

まず、基本的な性質としてa * b = GCD(a,b) * LCM(a,b)がある。両辺を2回GCD(a,b)で割ると

 \displaystyle \frac{a}{GCD(a,b)} * \frac{b}{GCD(a,b)} = \frac{LCM(a,b)}{GCD(a,b)}

となる。これらは全て当然割り切れる。以降、簡単のために \frac{a}{GCD(a,b)} = A \frac{b}{GCD(a,b)} = B \frac{LCM(a,b)}{GCD(a,b)} = Cとおく。すると、上の式はA * B  = Cという形になる。

さて、このときABはそれぞれa,bGCD(a,b)で割った値であるということを考えれば、ABは互いに素であるということになる。つまり、C素因数分解した時に、その素因数ごとにABのどちらかへ属しているという形になる。そして、C \lt 2^{63}であるから、素数の小さい方から順にかけていった積を考えても、Cの素因数の個数は16種類以下にしかならないのが分かる。

つまり、C素因数分解し、その個数も求められたらその素因数のみでの積を出しておき、それが2数のどちらかに属するのかを全探索することができる(2^{16}通りなので間に合う)。そしてA+Bが最小になるものを求めれば、a+b = GCD(a,b) * (A+B)なので、正しい答えを得られる。

そこで、唯一問題として残るのがC素因数分解である。とても巨大なので、間に合わないじゃないか、という感じがあるが、それを高速に実現するPollard's rho algorithmというものがある。具体的にはまだ自分も理解しきれてはいないが、合成数から非自明な約数を高速に見つけ出すアルゴリズムになっている。また、このアルゴリズムの性質上、素数がこの判定器に入ると終了しなくなる可能性がとても高いので、それを事前に避けるために巨大な整数の素数判定を高速に行う方法としてMiller–Rabin primality testというものを利用する。この2つに関しては"INTRODUCTION TO ALGORITHMS"にも書かれているので、そちらも見ながらもう少し理解を深めたい。

この2つには確率的な要素が絡むので、出来る限りの利用を避けるために普通に列挙可能な十分に小さい素数(今回は300000以下とした)に関してはいつもどおり列挙し素因数分解をするというハイブリッドの形をとって上の関数が呼ばれる回数を抑える工夫になっている。また、mod上での掛け算やべき乗が必要になるが、modが非常に大きいのでオーバーフローしないように工夫する必要がある。

実装上の参考になりました(特にPollard's rho algorithmのあたり)。また、Miller–Rabin primality testは友人が軽くライブラリ化していたものを拝借して、実装した(感謝しかない)。

実装(C++)

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <utility>
#include <map>
#include <climits>
using namespace std;

typedef long long ll;
#define rep(i,n) for(int (i)=0;(i)<(int)(n);++(i))
#define each(itr,c) for(__typeof(c.begin()) itr=c.begin(); itr!=c.end(); ++itr)
#define all(x) (x).begin(),(x).end()
#define pb push_back
#define fi first
#define se second

#define long unsigned long long

long mod_mul(long a, long b, long mod){
    long res=0;
    a%=mod;
    while(b){
        if(b&1){
            res+=a;
            if(res>mod) res-=mod;
        }
        a <<= 1;
        if(a>mod) a-=mod;
        b >>= 1;
    }
    return res;
}

// x^n mod
long mod_pow(long x, long n, long mod){
  long res=1;
  x%=mod;
  while(n){
    if(n&1) res=mod_mul(res,x,mod);
    x=mod_mul(x,x,mod);
    n>>=1;
  }
  return res;
}

// Miller-Rabin Primality Test
bool miller_rabin(long n){
    // using primes up to 37 is enough to judge 64 bit integer
    const int primes[] = {2,3,5,7,11,13,17,19,23,29,31,37};
    const int nprime = 12;
    long m = n-1, s=0;

    if(n<=1) return false;  // 1 or 0 or negative
    if(n%2==0) return n==2; // even number
    for(int i=0; i<nprime; i++){ //embeded primes and its multiple
        if(n == primes[i]) return true;
        if(n%primes[i] == 0) return false;
    }

    while(m%2==0){ s++; m>>=1; } // n=m*2^s+1
    // Miller-Rabin primality test
    for(int i=0; i<nprime; i++){
        long a = primes[i];
        long y = mod_pow(a, m, n);
        if(y==1) continue;
        bool flg=true;
        for(int j=0; flg && (j<s); j++){
            if(y==n-1) flg=false;
            y = mod_mul(y, y, n);
        }
        if(flg==false) continue;
        return false;
    }
    return true;
}

long pollard_rho(long n, int c){
    long x=2,y=2,d=1;
    while(d==1)
    {
        x = mod_mul(x,x,n) + c;
        y = mod_mul(y,y,n) + c;
        y = mod_mul(y,y,n) + c;
        d = __gcd((x>y)?x-y:y-x,n);
    }
    if(d==n) return pollard_rho(n,c+1);
    return d;
}

const int MAX_PRIME=300000;
vector<int> primes;
vector<bool> is_prime;

void init_primes(){
    is_prime = vector<bool>(MAX_PRIME+1,true);
    is_prime[0] = is_prime[1] = false;
    for(int i=2; i<=MAX_PRIME; ++i){
        if(is_prime[i]){
            primes.push_back(i);
            for(int j=i*2; j<=MAX_PRIME; j+=i) is_prime[j]=false;
        }
    }
}

bool isPrime(long n)
{
    if(n<=MAX_PRIME) return is_prime[n];
    else return miller_rabin(n);
}

void factorize(long n, map<long,int> &factors){
    if(isPrime(n)) factors[n]++;
    else{
        for(int i=0; i<primes.size(); ++i){
            int p=primes[i];
            while(n%p==0){
                factors[p]++;
                n/=p;
            }
        }
        if(n!=1){
            if(isPrime(n)) factors[n]++;
            else{
                long d=pollard_rho(n,1);
                factorize(d,   factors);
                factorize(n/d, factors);
            }
        }
    }
}

pair<long,long> solve(long GCD, long LCM)
{
    map<long,int> factors;
    factorize(LCM/GCD,factors);

    vector<long> mul_factors;
    each(itr,factors)
    {
        long tmp=1;
        rep(i,itr->second) tmp*=itr->first;
        mul_factors.pb(tmp);
    }

    long min_sum=ULLONG_MAX;
    long ansx=1, ansy=1;
    int M=mul_factors.size();
    rep(mask,1<<M)
    {
        long tx=1, ty=1;
        rep(i,M)
        {
            if(mask>>i&1) tx*=mul_factors[i];
            else ty*=mul_factors[i];
        }
        if(tx+ty<min_sum)
        {
            if(tx>ty) swap(tx,ty);
            min_sum=tx+ty;
            ansx=tx;
            ansy=ty;
        }
    }

    return pair<long,long>(ansx,ansy);
}

int main()
{
    init_primes();

    long GCD,LCM;
    while(cin >>GCD >>LCM)
    {
        pair<long,long> ans=solve(GCD,LCM);
        cout << GCD*ans.fi << " " << GCD*ans.se << endl;
    }
    return 0;
}