山傘のプログラミング勉強日記

プログラミングに関する日記とどうでもよい雑記からなるブログです。

Codeforces Round #511 (Div. 2) に参加した感想

問題

Dashboard - Codeforces Round #511 (Div. 2) - Codeforces

Codeforcesの記事を書くのは久しぶりですが、記事にしていない回も参加はしていました。

A. Little C Loves 3 I

 a, b, c 3 の倍数ではない整数とします。

 a + b + c = n

を満たす整数  a, b, c を求める問題です。

 n 3 の倍数かそうでないかで、場合分けをすればよいです。

コード

import java.util.Scanner;

public class ProbemA {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        scan.close();
        if(n == 3) {
            System.out.println("1 1 1");
            System.exit(0);
        }
        if(n % 3 == 0) {
            int a = n / 3;
            int b = n / 3;
            int c = n / 3;
            if(a % 3 == 0) {
                a -= 2;
                b += 1;
                c += 1;
                System.out.println(a + " " + b + " " + c);
            }else {
                System.out.println(a + " " + b + " " + c);
            }
        }else {
            int a = n - 3;
            int b = 2;
            int c = 1;
            System.out.println(a + " " + b + " " + c);
        }
    }
}

B. Cover Points

第一象限に内にある格子点が与えられます。格子点は整数なので、全ての格子点を内部に持つ三角形の斜辺の方程式の候補は、

 y = -x + n

となります。 条件を満たす  n の最小値を答えれば良いです。

 n_i = x_i + y_i

と変形できるので、この  n_i の最大値を求めれば良いです。

コード

import java.util.Scanner;

public class ProblemB {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[]x = new int[n];
        int[]y = new int[n];
        for(int i = 0; i < n; i++) {
            x[i] = scan.nextInt();
            y[i] = scan.nextInt();
        }
        scan.close();
        int max = 0;
        for(int i = 0; i < n; i++) {
            max = Math.max(max, x[i]+y[i]);
        }
        System.out.println(max);
    }
}

C. Enlarge GCD

WAとTLEを出しまくった問題です。コンテスト後に何度も挑戦してやっとACとなりました。基本的な方針は分かっていたんですが、ACを取るまでに苦労しました。

考え方

 A = \{a_1, a_2, \cdots, a_n \} の最大公約数を  g とします。 A の要素を  g で割ります。

 A^{'} =  \dfrac{1}{g}A

 A^{'} の重複している要素を消したいので、 HashSet に  A^{'} の要素を格納することと、その要素の個数を数えます。また、重複していない要素の個数は  1 となります。

このようにすることで、重複している要素がなくなり、計算量の減少が見込めます。

また、 g で割る理由を考えると、 A = {2, 4, 6, 8} としたとき、最大公倍数は  2 となります。

 A の部分集合  \{4, 8\} の最大公約数は  4 になり、題意を満たします。また、 A の最大公約数  2 でこの部分集合を割ると、 \{2, 4\} となりこの最大公約数は  2 となりますが、割る前の部分集合を考えると、  2 \times 2 = 4 となり題意を満たします。

つまり、 g で割った後の集合の部分集合の最大公約数が  1 でなければ、 g より大きい部分集合の最大公約数が存在するということです。

ここで、 g より大きい部分集合の最大公約数が存在する部分集合の要素の最大個数を求めることが必要です。そのような値を  m とすると、答えは  n - m となります。ただし、 m = 1 ならば答えは  -1 です。

素数を利用して  m を求める

HashSet に  A^{'} の要素を格納したとします。

 15000000 までのエラトステネスの篩を作成します。

また、素数をキーにもち、値が  m のHashMapを用意します。

HashSetの要素  k を一つずつしらべ、その値が素数ならば、キーが  k のMapの値を  k の個数分増やします。

 k素数でない場合、  k 以下の素数 k素数になるまで割り続けます。ここで、 k を割り切る素数 p とすると、 p をキーに持つMapの値を  1 増やします。

例を挙げると、HashSetの要素が  12 のとき、 12 2 (素数)で割ることができるので、

 12 → 6 → 3

となり、 3素数なので、キーが  2 3 のMapの値を  1 増やします。

まあ、やっていることは素因数分解みたいなものです。

コード

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

public class ProblemC {
    static int d = 15000000;
    static boolean[] isPrime = new boolean[d + 1];

    public static void main(String[] args) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(reader.readLine());
        StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
        int[]a = new int[n];
        for(int i = 0; i < n; i++) {
            int t = Integer.parseInt(tokenizer.nextToken());
            a[i] = t;
        }

        int[]k = new int[d + 1];
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        Set<Integer> set = new HashSet<Integer>();
        int g = gcd(a[0], a[1]);
        for(int i = 2; i < n; i++) {
            g = gcd(g, a[i]);
        }

        for(int i = 0; i < n; i++) {
            a[i] = a[i] / g;
            k[a[i]]++;
            set.add(a[i]);
        }
        
        // エラトステネスの篩
        aryPrime();

        // 素数リスト
        List<Integer> list = new ArrayList<Integer>();

        int max = (int)Math.sqrt(d);
        for(int i = 2; i <= max; i++) {
            if(isPrime[i]) {
                list.add(i);
            }
        }

        for(int i : set) {
            if(isPrime[i]) {
                if(map.containsKey(i)) {
                    map.put(i, map.get(i) + k[i]);
                }else {
                    map.put(i, k[i]);
                }
            }else {
                int t = i;
                for(int j = 0; j < list.size(); j++) {
                    if(isPrime[t]) {
                        if(map.containsKey(t)) {
                            map.put(t, map.get(t) + k[i]);
                        }else {
                            map.put(t, k[i]);
                        }
                        break;
                    }else {
                        int p = list.get(j);
                        if(t % p == 0) {
                            if(map.containsKey(p)) {
                                map.put(p, map.get(p) + k[i]);
                            }else {
                                map.put(p, k[i]);
                            }
                            while(t % p == 0) {
                                t = t / p;
                            }
                        }
                    }
                }
            }
        }
        int ans = 0;
        for(int key : map.keySet()) {
            int t = map.get(key);
            if(ans < t) {
                ans = t;
            }
        }
        if(ans == 0) {
            System.out.println(-1);
        }else {
            System.out.println(n - ans);
        }

    }
    static void aryPrime(){
        int l = isPrime.length;
        for(int i = 0; i < l; i++) {
            isPrime[i] = true;
        }
        isPrime[0] = false;
        isPrime[1] = false;
        int max = (int)Math.sqrt(l);
        for(int i = 2; i <= max; i++) {
            if(isPrime[i]) {
                for(int j = i * 2; j < l; j += i) {
                    isPrime[j] = false;
                }
            }

        }
    }
     static int gcd(int m, int n) {
        if(m < n) return gcd(n, m);
        if(n == 0) return m;
        return gcd(n, m % n);
    }
}

感想

C問題が難しかったんですが、解けて良かったです。数学よりの問題でしたが、実装が結構難しかったです。

Codeforcesの過去問などにはまだ手を出せていないので、yukicoderやAtCoderやAOJの問題を解いていきたいです。