エンジニアの競馬情報収集ブログ

最近は5割が競馬の話、3割がLoL/LJLの話、2割が技術的な話な感じ。たまにその他の話。

AOJ 1232: Calling Extraterrestrial Intelligence Again

問題

問題文訳

1974年12月16日の土曜日、プエルトリコアレシボ天文台に、地球外知的生命体から人類にメッセージが送られてきた。メッセージは1679ビットで、これは 23 x 73 の長方形の図に変換される。23と73はともに素数であり、面積が1679になり、かつ1辺が1より長い唯一の長方形である。ただし、メッセージはぴったり長方形の図に変換できるとは限らない。

我々は小さなプロジェクトを計画している。このプロジェクトにおけるあなたの仕事は、"最も適した"長方形を探すことだ。整数 m (m > 4) と、正の分数 a/b (a/b <= 1) が与えられるので、幅と高さがともに素数で、面積はmを超えず、幅と高さの比がa/b以上1以下になる長方形の中で、最も面積が最大になるような長方形を探せ。

言い換えると、 m (m > 4), a/b (a/b <= 1) が与えられるので、 pq <= m かつ a/b <= p/q <= 1 となるような素数の対p, qの中で、 pqが最大となるようなものを探せ。

入力は、一行にm, p, qが空白区切りが与えられ、m, p, q の組み合わせが最大2000行ある。入力の最後は "0 0 0" で表される。 4 < m < 100000, 1 <= a <= b <= 1000 である。

出力は、ひとつの m, p, q の組み合わせに対して、幅と高さの対を空白区切りで1行で出力せよ。

(訳注: 入出力は入出力例を見たらわかりやすいと思う)

ソースコード解説

Primeクラス

素数問題の時に使う自作クラス。

/**
 * 整数を数え上げたりするクラス
 */
class Prime{
    static boolean[] isPrime;
    static int[] count;
    static ArrayList<Integer> list = new ArrayList<Integer>();
    
    /**
    * nまでの計算をエラトステネスの篩で行う
    */
    static void init(int n){
        isPrime = new boolean[n+1];
        isPrime[0] = false; isPrime[1] = false;
        for(int i = 2; i <= n; i++) isPrime[i] = true;
        // ふるい
        for(int i = 2; i < (n - 3) / 2; i++){
            if(isPrime[i]){
                for(int j = 2; j * i <= n; j++){
                    isPrime[j * i] = false;
                }
            }
        }
 
        // 数え上げと集合の作成
        count = new int[n+1];
        count[0] = 0;
        for(int i = 1; i <= n; i++){
            int gain = 0;
            if(isPrime[i]){
                gain = 1;
                list.add(i);
            }
            count[i] = count[i-1] + gain;
        }
    }
    /**
    * 入力されたiに対して、素数かどうか判定をして返す
    * @return true: iが素数である    false: iが素数でない
    */
    static boolean calcPrime(int i){
        if(i <= 1) return false;
        if(i == 2) return true;
        if(i % 2 == 0) return false;
        for(int j = 3; j <= Math.sqrt(i); j+=2){
            if(i % j == 0) return false;
        }
        return true;
    }
}

このクラスは Prime.init(n) とすることで、nまでの整数までの配列を作成し、エラトステネスの篩を行います。Prime.isPrine(i) で i (i <= n) が素数かどうかを調べることができます。また、Prime.list にはn以下の素数のListが作成されます。

回答本体部分

class Main extends MyUtil{
    // public static Graph g;
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // prime の準備
        Prime.init(100000);
        
        while(true){
            // 入力
            String[] in = br.readLine().split(" ");
            int m = Integer.parseInt(in[0]);
            int a = Integer.parseInt(in[1]);
            int b = Integer.parseInt(in[2]);
            if(m == 0 && a == 0 && b == 0) break;
            
            int pointer_p = 0, pointer_q = Prime.list.size() - 1;
            int ans_m = 0, ans_p = 0, ans_q = 0;
            
            while(pointer_p <= pointer_q){
                int p = Prime.list.get(pointer_p);
                int q = Prime.list.get(pointer_q);

                if(p*Prime.list.get(pointer_q) > m){
                    pointer_q--;
                    continue;
                }
                
                for(int i = pointer_p; i <= pointer_q; i++){
                    int qq = Prime.list.get(i);
                    if((double)a/b <= (double)p/qq){
                        if(ans_m < p*qq){
                            ans_m = p*qq;
                            ans_p = p;
                            ans_q = qq;
                        }
                    }
                }
                pointer_p++;
            }
            
            System.out.println(ans_p + " " + ans_q);
        }
    }
}
  • まず、100000までのPrime.listを作成する
  • pointer_p と pointer_q を用意する。pointer_pは最初にリストの左端を、pointer_qは最初にリストの右端を指している
  • pointer_p に対して、pointer_qが、p*qがmを超えない最大のqの場所を指すようにする。(pointer_qを左に動かしていく)
  • p = list[pointer_p] *1 , q = list[pointer_q] として、qqをpからqの間で変化させる。
  • p*qqで、a/b <= p/qq を満たすp,qqのうち、面積がこれまでの最大値を超える場合はそれらの値を記録する
    • pointer_p <= pointer_q が保たれている限り、p < qq すなわち p/qq <= 1 は保証されているのでここではチェックしていない
  • 最終的に面積が最大になった p, q を出力する。

はまりどころ

  • pointer_pとpointer_qを左右から近づけていくだけだと、a/b <= p/q <= 1 を満たすようなものがうまく見つけられないことがあるので、しっかりと探索する必要がある。
  • 100000以下の素数の数は9592個 ≒ 104個とすると、素数だけに絞って全探索すると 104 * 104 = 108 となってしまい、これではおそらく間に合わないので、pとqを左右から近づけていく感じで枝刈りをする必要がある。

*1:説明のためにlist.get(pointer_p)を簡易的に表記している