AtCoder Beginner Contest 221(Rated範囲外)

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

A - Seismic magnitude scales

問題

思考過程
  1. A-B32倍する。
  2. 632倍したときにintの範囲を超えないか考え、4 \times 6 \lt 31だが念のためlong型にしておいた。
import java.util.Scanner;

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

        long ans = 1;
        for (int i = b; i < a; i++) {
            ans *= 32;
        }
        System.out.println(ans);
    }
}

ここまで1分12秒+0ペナ。485位。


B - typo

問題
Bとしては結構大変だった。というか大変にしてしまったかも。

思考過程
  1. とりあえず最初から一致している場合は"Yes"。残りは1回入れ替えるケースだけ考える。
  2. 1回だけ入れ替えて一致するためには、まず連続する2文字1箇所だけが不一致である必要がある。
  3. S_i \ne T_iであるiをリスト化する。
  4. リストの要素数2でない場合は"No"。
  5. リストの2要素の差が1でない場合は"No"。
  6. あとは入れ替えて一致するかどうかを判定する。
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

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

        // 1
        if (s.equals(t)) {
            System.out.println("Yes");
        } else {
            // 3
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < s.length(); i++) {
                if (s.charAt(i) != t.charAt(i)) {
                    list.add(i);
                }
            }
            // 4
            if (list.size() != 2) {
                System.out.println("No");
                return;
            }
            // 5
            int a = list.get(0);
            int b = list.get(1);
            if (a + 1 != b) {
                System.out.println("No");
                return;
            }
            // 6
            if (s.charAt(a) == t.charAt(b) && s.charAt(b) == t.charAt(a)) {
                System.out.println("Yes");
            } else {
                System.out.println("No");
            }
        }
    }
}

ここまで4分55秒+0ペナ。666位。


C - Select Mul

問題

思考過程
  1. Nの各桁を2グループに分けた後は、グループ内で降順に並べ替えるのが最適。
  2. グループの分け方は全通り試しても、2^{10}通り以下には収まるので全探索する。

解説を見て、先に降順ソートしてからグループ分けすれば余計なコードがだいぶ減ったと気付いた。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        char[] s = sc.next().toCharArray();
        sc.close();

        int n = s.length;
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = s[i] - '0';
        }

        long ans = 0;
        int end = 1 << n;
        for (int i = 0; i < end; i++) {
            List<Integer> list1 = new ArrayList<>();
            List<Integer> list2 = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                if ((i >> j & 1) == 1) {
                    list1.add(a[j]);
                } else {
                    list2.add(a[j]);
                }
            }
            // 各グループを降順に並べ替え
            Collections.sort(list1, Collections.reverseOrder());
            Collections.sort(list2, Collections.reverseOrder());
            // 空または0のみの場合はスキップ
            if (list1.isEmpty() || list2.isEmpty()
                    || list1.get(0) == 0 || list2.get(0) == 0) {
                continue;
            }

            int v1 = 0;
            for (int j = 0; j < list1.size(); j++) {
                v1 *= 10;
                v1 += list1.get(j);
            }
            int v2 = 0;
            for (int j = 0; j < list2.size(); j++) {
                v2 *= 10;
                v2 += list2.get(j);
            }
            long val = (long) v1 * v2;
            ans = Math.max(ans, val);
        }
        System.out.println(ans);
    }
}

ここまで9分18秒+0ペナ。199位。


D - Online games

問題

思考過程
  1. 各日の人数をいもす法で求めたいが、A, Bの制約が大きくて配列では駄目なので、マップ<K日目以降、V人>上で行う。
  2. i日目の人数をV_iとすると、日数分ループしてD_{V_i}をインクリメントしていきたいところだが、1ずつ加算する代わりにマップのキー間の差を加算する。
import java.util.Scanner;
import java.util.TreeMap;

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

        // いもす法
        Integer[] arr = map.keySet().toArray(new Integer[0]);
        int val = 0;
        for (Integer key : arr) {
            val += map.get(key);
            map.put(key, val);
        }

        int[] d = new int[n + 1];
        for (int i = 1; i < arr.length; i++) {
            // e日間の間v人が続いている
            int e = arr[i] - arr[i - 1];
            int v = map.get(arr[i - 1]);
            d[v] += e;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= n; i++) {
            sb.append(d[i]).append(' ');
        }
        sb.deleteCharAt(sb.length() - 1);
        System.out.println(sb.toString());
    }
}

ここまで18分28秒+0ペナ。236位。


E - LEQ

問題
過去問で一度同じようなことをやったことがある覚えはあるのだが、どの問題かはすぐに出てこない。

思考過程
  1. A'_1A'_kの組み合わせを数えることを考える。
  2. BITを使い、前から順に位置A_i+1していくと、A_iについて求める際、A_{i-1}までの情報がBITに入っているので、A_i以下の値の数を取得することで、A_iとペアにできる数がわかる。
  3. ただし、A_iの制約が大きいので、あらかじめ座標圧縮をしておく。
     
  4. 基本的な構造はこれでよさそうだが、A'_kとペアになる各A'_1について、間の要素を含めるかどうかを自由に決められるので、2^{k-2}倍しなければならない。仮に単調増加列であれば、
    • A'_k = A_2の場合について1通り
    • A'_k = A_3の場合について2+1通り
    • A'_k = A_4の場合について4+2+1通り
       \cdots
       
  5. この重み付けを実現するためには、BITに追加する際+1するのではなく、+2^{N-1-i}する形にしておき、取得した合計を2^{N-i}で割って帳尻を合わせるようにする。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[] sa = br.readLine().split(" ");
        long[] a = new long[n];
        for (int i = 0; i < n; i++) {
            a[i] = Integer.parseInt(sa[i]);
        }
        br.close();

        int mod = 998244353;
        int m2 = (mod + 1) / 2;  // 2の逆元
        int[] p = new int[n];    // 2のi乗
        long[] pi = new long[n]; // 2のi乗の逆元
        p[0] = 1;
        pi[0] = 1;
        for (int i = 1; i < p.length; i++) {
            p[i] = p[i - 1] * 2 % mod;
            pi[i] = pi[i - 1] * m2 % mod;
        }

        int[] b = zaatu(a);
        BIT bit = new BIT(n);
        long ans = 0;
        for (int i = 0; i < n; i++) {
            if (i > 0) {
                long v1 = bit.sum(b[i]) % mod;
                long v2 = pi[n - i];
                ans += v1 * v2 % mod;
            }
            bit.add(b[i], p[n - 1 - i]);
        }
        ans %= mod;
        System.out.println(ans);
    }

    // 以下ライブラリ

    static int[] zaatu(long[] a) {
        TreeMap<Long, Integer> map = new TreeMap<>();
        for (int i = 0; i < a.length; i++) {
            map.put(a[i], null);
        }
        Long[] arr = map.keySet().toArray(new Long[0]);
        int cnt = 0;
        for (Long i : arr) {
            cnt++;
            map.put(i, cnt);
        }
        int[] b = new int[a.length];
        for (int i = 0; i < a.length; i++) {
            b[i] = map.get(a[i]);
        }
        return b;
    }

    static class BIT {
        int n;
        long[] arr;

        public BIT(int n) {
            this.n = n;
            arr = new long[n + 1];
        }

        void add(int idx, long val) {
            for (int i = idx; i <= n; i += i & -i) {
                arr[i] += val;
            }
        }

        long sum(int idx) {
            long sum = 0;
            for (int i = idx; i > 0; i -= i & -i) {
                sum += arr[i];
            }
            return sum;
        }
    }
}

ここまで34分28秒+0ペナ。158位。


F - Diameter set

問題
公式解説に書かれているレベルの解法は合っていたのに、手抜きした結果、多分木の中心または中心を含む辺を正しく求められていなくて通らず。

思考過程
  1. 直径が偶数(中心が頂点)の場合と、奇数(中心が辺の中央)の場合に分けて考える。
     
  2. 偶数の場合
    1. 例2を見て、中心から距離が\frac{D}{2}である点の数をX個とすると、X個の中から2個以上を選ぶ通り数を求めればよい?
    2. それだと、中心以外で枝分かれしている場合がおかしい。
    3. 中心の隣を根とした部分木ごとに分け、各部分木で根から距離が\frac{D}{2}-1である点の数をY個とすると、部分木iではY_i個の中から1個以下を選び、1個選ぶ部分木が2個以上という条件に合致する通り数を求めることになる。
       
  3. 奇数の場合
    1. 中心となる辺で切った2つの部分木(切った辺の両端がそれぞれの根)について、根から距離が\lfloor \frac{D}{2} \rfloorである点の数をそれぞれY_1, Y_2個とする。
    2. 両方から1つずつ選ぶ必要があるので、Y_1 \times Y_2通り。

なお、偶数の場合の2.以降に考察が進んだのは、奇数の場合を考えた後。


GとHは問題を開いてもいない。



終結果:ABCDEの5完1500点、34分28秒、325位、パフォーマンス2029相当
レート変動:2005(Unrated)


Fが解けなかったのは悔しかったが、最近のボロボロっぷりからすればレート相応の結果だっただけでもよかった。


記事のタイトルですが、(Unrated)だとコンテスト自体に問題があってUnratedだったかのように見えてしまうと思ったので、(Rated範囲外)に変えることにしました。