鹿島建設プログラミングコンテスト2020(AtCoder Regular Contest 110)

コンテスト前のレート:1766
レート通りのパフォーマンスが得られる順位:551位(1200点、51分22秒)

A - Redundant Redundancy

問題

思考過程
  1. どれで割っても1余る数は、どれで割っても割り切れる数+1と同じこと。
  2. 2Nの最小公倍数を求めて1を足す。
  3. N=30の場合に10^{13}以下に収まっていることを確認して提出。
import java.math.BigInteger;
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();

        long ans = 1;
        for (int i = 2; i <= n; i++) {
            ans = lcm(ans, i);
        }
        System.out.println(ans + 1);
    }

    // 以下ライブラリ

    static long lcm(long a, long b) {
        BigInteger ba = BigInteger.valueOf(a);
        BigInteger bb = BigInteger.valueOf(b);
        return ba.multiply(bb).divide(ba.gcd(bb)).longValue();
    }
}

ここまで1分35秒+0ペナ。56位。


B - Many 110

問題
結構時間かかってしまったし、公式解説と比べると、以下の思考過程は遠回りしていて無理矢理帳尻合わせしている感じ。

思考過程
  1. サンプルを見た感じでは、10^{10}からTに含まれる”110”の数cntを引いて、-1したくらい。
  2. Tが"1"の場合は2 \times 10^{10}。まとめて処理しにくそうなので、まずこれだけ場合分けしてみる。
     
  3. Tは、"xx110110...110yy"という形をしているので、途中"110"が何回続いたかを数えつつ、最初と最後の部分だけを抜き出す。
  4. 最初の"xx"の部分は"0"か"10"かしかないので、その部分を取り除いて"110"から始まるようにして処理することにする。
  5. TSに含まれるならば、最初と最後の"xxyy"の部分は"110110"に含まれるはず("1011"でよかったかもしれない)。これを満たさない場合は0
     
  6. "xx"と"yy"が共に空文字列ではない場合は、サンプルのように10^{10}-cnt-1
  7. そうでなければ自由度が1増えるので10^{10}-cnt? →思考過程3の実装にバグがありWA。直してもまだ3ケースWA。
  8. "xx"と"yy"が共に空文字列の場合は、自由度+210^{10}-cnt+1だった。
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();
        String t = sc.next();
        sc.close();

        long mx = 10000000000L;
        // 思考過程2
        if (t.equals("1")) {
            System.out.println(2 * mx);
            return;
        }

        // 思考過程4
        StringBuilder sb = new StringBuilder();
        if (t.startsWith("01")) {
            sb.append("0");
            t = t.substring(1);
        } else if (t.startsWith("101")) {
            sb.append("10");
            t = t.substring(2);
        }

        // 思考過程5
        int cnt = 0;
        char[] ct = t.toCharArray();
        for (int i = 0; i < ct.length - 2; i+=3) {
            if (ct[i] == '1' && ct[i + 1] == '1' && ct[i + 2] == '0') {
                cnt++;
            } else {
                break;
            }
        }
        for (int i = cnt * 3; i < ct.length; i++) {
            sb.append(ct[i]);
        }
        String x = sb.toString();
        if ("110110".indexOf(x) == -1) {
            System.out.println(0);
            return;
        }

        mx -= cnt;
        if ("110".indexOf(x) == -1) {
            System.out.println(mx - 1);
        } else if (x.equals("")) { // 思考過程8
            System.out.println(mx + 1);
        } else {
            System.out.println(mx);
        }
    }
}

ここまで26分14秒+2ペナ。635位。


C - Exoswap

問題

思考過程
  1. まずは1Nを両端に持っていこうとしてみる。
  2. その場合、1から左端までの間と、Nから右端までの間を順に1回ずつ選ぶ以外に方法はない。
  3. 1を左端に移動させた時点で、元々1があった場所をA_1とするならば、1A_1-1までが揃っている必要があり、次はA_1をどこか右の方からA_1まで動かしてきて\cdotsと続けていくことができる。
  4. 実装上は、1から順に処理していき、既に操作している位置を再度使う必要が生じた時点で-1とする。 →19ケースWA
  5. N-1箇所全て使っているかの確認が漏れていた。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.LinkedHashSet;

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(" ");
        int[] p = new int[n];
        for (int i = 0; i < n; i++) {
            p[i] = Integer.parseInt(sa[i]) - 1;
        }
        br.close();

        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[p[i]] = i;
        }

        LinkedHashSet<Integer> set = new LinkedHashSet<>();
        for (int i = 0; i < n; i++) {
            for (int j = a[i]; j > i; j--) {
                if (set.contains(j)) {
                    System.out.println(-1);
                    return;
                }
                set.add(j);
                int pl = p[j - 1];
                int pr = p[j];
                a[pl]++;
                a[pr]--;
                p[j - 1] = pr;
                p[j] = pl;
            }
        }
        // 思考過程5
        if (set.size() != n - 1) {
            System.out.println(-1);
            return;
        }

        PrintWriter pw = new PrintWriter(System.out);
        for (int i : set) {
            pw.println(i);
        }
        pw.flush();
    }
}

ここまで40分54秒+1ペナ。375位。


F - Esoswap

問題
D問題は数学色が強そうでよくわからず。
E問題は簡単なDPをしてみたが、重複カウントが出ていそうで、排除方法が全く見当もつかず。

なんとなくこのF問題をこねくり回していたら1つの方法を思いつき、証明できなかったが駄目元で投げてみたら通ってしまった。
正当性は不明。
思考過程3のところで、同じ状態のループにならない保証があるのかわからないがどうなのか・・・。

思考過程
  1. 整数iが位置iに移動するときの最後の1手を考えると、以下のいずれかしかない。
    • 位置iに整数jが存在して位置i+jに整数iが存在する状態でiを宣言する
    • 位置0に整数iが存在する状態で0を宣言する。
  2. 簡単なのは後者なので、例1や、他の適当なN=8程度の数列を手で動かしてみて、0を宣言し続けた場合にどうなるかを見てみる。
  3. 昇順になりきらずに先頭が0になってしまう場合もあるが、P_i=iとなっていないところを適当に動かせば、その内P_0=0でなくなりそう。
  4. 結局、数列を前から見ていって最初にP_i \neq iとなった場所を宣言し続けるだけ。(ほとんどの場合は先頭になる想定)
  5. 上限を超えても昇順にできていなかったら-1を出力する処理を一応付け足しておく。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

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(" ");
        int[] p = new int[n];
        for (int i = 0; i < n; i++) {
            p[i] = Integer.parseInt(sa[i]);
        }
        br.close();

        int mx = 200000;
        List<Integer> list = new ArrayList<>();
        while (list.size() <= mx) {
            boolean flg = true;
            for (int i = 0; i < n; i++) {
                if (p[i] != i) {
                    flg = false;
                    list.add(i);
                    int j = (i + p[i]) % n;
                    int t = p[i];
                    p[i] = p[j];
                    p[j] = t;
                    break;
                }
            }
            if (flg) {
                break;
            }
        }
        if (list.size() > mx) {
            System.out.println(-1);
        } else {
            PrintWriter pw = new PrintWriter(System.out);
            pw.println(list.size());
            for (int i : list) {
                pw.println(i);
            }
            pw.flush();
        }
    }
}

ABCFと通した時点で104分48秒+3ペナ。63位。

残り15分でD問題を少し考えるふりをするが、既に望外の結果なので順位表を眺めていた。



終結果:ABCFの4完、119分48秒、78位、パフォーマンス2600
レート変動:1766→1882(+116)


まぐれ一発大当たりで、初の橙パフォが出てHighestも62も更新。
最高パフォも300近く更新しているし、本番中に解けたDifficultyの最高も先週2251→2394に上がったばかりなのに今回さらに2719に。
1年に1回あればいい方くらいの大成功だったかと。

まあ実力が伴っているわけではないので、レートはまたすぐ下がると思う。
なんとか1800維持できないものかな・・・。

ここ2ヶ月のレート急落は、まともに新しいことを覚えていない上に、これまでにやってきた問題を徐々に忘れてできなくなってきていることに原因があると見て、最近は可能な限りくじかつをやるようにしている。
これで大失敗を減らした上で、前回や今回のようにたまに大成功を出せたらいいなというところ。