AtCoder Beginner Contest 206(Rated範囲外)

コンテスト前のレート:2074
(Ratedだとしたら)レート通りのパフォーマンスが得られる順位:286位(1500点、43分52秒)

A - Maxi-Buying

問題

思考過程
  1. とりあえずN \times 1.08を計算する。
  2. int型にキャストすれば、切り捨ての値を求められる。
  3. あとは計算結果と206を比較して出力し分ける。
  4. 面倒だが出力する文字列は一応コピペする。("so-so"を"50-50"と手書きしそうになった)
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        sc.close();

        int x = (int) (n * 1.08);
        if (x > 206) {
            System.out.println(":(");
        } else if (x == 206) {
            System.out.println("so-so");
        } else {
            System.out.println("Yay!");
        }
    }
}

ここまで1分36秒+0ペナ。269位。


B - Savings

問題

思考過程
  1. i日目に入っている金額は\frac{i(i+1)}{2}円なので、N以上になるまで無制限にループを回してもO(\sqrt{N})
  2. 毎回上記の通り計算してもいいが、題意通り毎回iを足してもよい。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        sc.close();

        int s = 0;
        for (int i = 1; ; i++) {
            s += i;
            if (s >= n) {
                System.out.println(i);
                return;
            }
        }
    }
}

ここまで2分22秒+0ペナ。63位。


C - Swappable

問題

思考過程
  1. N個から2個を選ぶ全組合せ_NC_2=\frac{N(N-1)}{2}通りから、同じ値である要素の中から2個を選ぶ組合せを引く。
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            int a = sc.nextInt();
            map.put(a, map.getOrDefault(a, 0) + 1);
        }
        sc.close();

        long ans = (long) n * (n - 1) / 2;
        for (int v : map.values()) {
            ans -= (long) v * (v - 1) / 2;
        }
        System.out.println(ans);
    }
}

ここまで4分23秒+0ペナ。94位。


D - KAIBUNsyo

問題

思考過程
  1. 最終的に、前からi番目と後ろからi番目の値を同じにする必要がある。
  2. 例えば、A=(1, 2, 2, 1, 3, 4, 1)のような場合、2, 3, 4を同じ値にする。
  3. UnionFindを使って前からi番目と後ろからi番目の値を連結していけば、同じ値にするものが同じ連結成分になっている。
     
  4. 答えは異なる連結成分を連結した回数となり、それは「N-連結成分の個数」と求めることもできる。 →RE
  5. UnionFindのサイズはNではなくA_{max}だった。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
        }
        sc.close();

        int m = 200001;
        UnionFind uf = new UnionFind(m);
        int n2 = n / 2;
        for (int i = 0; i < n2; i++) {
            if (a[i] != a[n - 1 - i]) {
                uf.union(a[i], a[n - 1 - i]);
            }
        }
        System.out.println(m - uf.num);
    }

    // 以下ライブラリ

    static class UnionFind {
        int[] parent, size;
        int num = 0; // 連結成分の数

        UnionFind(int n) {
            parent = new int[n];
            size = new int[n];
            num = n;
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                size[i] = 1;
            }
        }

        void union(int x, int y) {
            int px = find(x);
            int py = find(y);
            if (px != py) {
                parent[px] = py;
                size[py] += size[px];
                num--;
            }
        }

        int find(int x) {
            if (parent[x] == x) {
                return x;
            }
            parent[x] = find(parent[x]);
            return parent[x];
        }
    }
}

ここまで8分30秒+1ペナ。89位。
5分経過後には300位程度。


E - Divide Both

問題

思考過程
  1. L \leq x \lt Rの全てのxについて、xの約数の倍数yの内x \lt y \le Rであるものを数える?
  2. それだと例えばx=12の時に、2の倍数と4の倍数を重複して数えてしまったりする。
     
  3. gを全探索して、gの倍数yの内L \le y \le Rであるものをz個として_zC_2の総和を求める?
  4. それだと最大公約数がgでない組合せを数えてしまう。
     
  5. 上記1.に戻って、重複して数えずに済む方法を考える。
  6. yの重複を避けようとしたときに、xの約数として考えるべきは、xの各素因数が1個以下である値のみ。
  7. 例えばx=2^p \times 3^q \times 5^rだとしたら、2, 3, 5, 6, 10, 15, 30の倍数を重複なく数えることを考える。
  8. これは包除原理を用いて、素因数の数が奇数なら足し、偶数なら引くことで、xR以下の範囲の「2の倍数の個数+3の倍数の個数+5の倍数の個数-6の倍数の個数-10の倍数の個数-15の倍数の個数+30の倍数の個数」のように求められる。
  9. 素因数が1個以下である値の列挙は、素因数の個数をnとして、2^n-1通り(全素因数が0個は除く)を調べる。
  10. これだけだと最大公約数がxの場合を数えてしまっているので、それを引く。
     
  11. まだ例3が合わない。
  12. x素数の場合、素因数が1つで最大公約数がxになるケースしか調べられないので、一応スキップさせてみる。
  13. さらにデバッグしてみると、x=1の場合におかしなことになっていたので、スキップさせてみる。
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int l = sc.nextInt();
        int r = sc.nextInt();
        sc.close();

        Eratosthenes era = new Eratosthenes(r);
        long ans = 0;
        for (int x = l; x < r; x++) {
            // 12, 13
            if (x == 1 || era.isSosuu(x)) {
                continue;
            }

            Map<Integer, Integer> map = era.bunkai(x); // 素因数分解
            Integer[] arr = map.keySet().toArray(new Integer[0]);
            int n = arr.length;
            int cnt = 0;
            // 9
            int end = 1 << n;
            for (int i = 1; i < end; i++) {
                int a = 1;
                int c = 0; // 使った素因数の個数
                for (int j = 0; j < n; j++) {
                    if ((i >> j & 1) == 1) {
                        a *= arr[j];
                        c++;
                    }
                }
                // 8
                int v = r / a - x / a;
                if (c % 2 == 1) {
                    cnt += v;
                } else {
                    cnt -= v;
                }
            }
            // 10
            int v = r / x - x / x;
            cnt -= v;
            ans += cnt;
        }
        System.out.println(ans * 2);
    }

    // 以下ライブラリ

    static class Eratosthenes {
        int[] div;

        public Eratosthenes(int n) {
            if (n < 2) return;
            div = new int[n + 1];
            div[0] = -1;
            div[1] = -1;
            int end = (int) Math.sqrt(n) + 1;
            for (int i = 2; i <= end; i++) {
                if (div[i] == 0) {
                    div[i] = i;
                    for (int j = i * i; j <= n; j+=i) {
                        if (div[j] == 0) div[j] = i;
                    }
                }
            }
            for (int i = end + 1; i <= n; i++) {
                if (div[i] == 0) div[i] = i;
            }
        }

        public Map<Integer, Integer> bunkai(int x) {
            Map<Integer, Integer> soinsu = new HashMap<>();
            while (x > 1) {
                Integer d = div[x];
                soinsu.put(d, soinsu.getOrDefault(d, 0) + 1);
                x /= d;
            }
            return soinsu;
        }

        public boolean isSosuu(int x) {
            return div[x] == x;
        }
    }
}

ここまで59分27秒+1ペナ。336位。



Fは解けず。
共有点を持つ区間同士に辺を張ったグラフを作って、連結成分ごとにGrundy数を求められればなどと考えていた。



終結果:ABCDEの5完1500点、64分27秒、402位、パフォーマンス1912相当
レート変動:2074(Unrated)


最近のABCではDまでを結構早く解けていることが多く、今回も1ミスはあったもののすんなり解けてよかった。

Eはあと20分早く通せないとレート2000台の平均には並べないっぽいけど、やっている時は全然できる気がしなかったので、通せただけでもマシだった。

Fができなかったことから、やっぱりEDPCやTDPCはそろそろ埋め切っておかないと駄目かなという感じ。
埋めるといっても、ほとんど解説ACすることになると思うけど。
確認したら、現在EDPCが23/26でTDPCが7/20。