キーエンス プログラミング コンテスト 2021

コンテスト前のレート:1863
レート通りのパフォーマンスが得られる順位:486位(1200点、42分00秒)

A - Two Sequences 2

問題
とにかく時間かかりすぎて辛い。

思考過程
  1. とりあえず現時点での最大値を達成したときのa_i(以下ma)とb_j(以下mb)を保持しながら、前からc_nを求めていくことを考える。
     
  2. b_n \gt mbの場合、ma \times b_nはそれまでの最大値より大きい。
  3. a_n \gt maの場合、a_nを採用する場合はb_nしか取れないので、a_n \times b_nがそれまでの最大値より大きいかどうかを調べる。
  4. これだと、例2の3行目以降が合わない。n=3の時、答えはa_2 \times b_3だが、a_3 \times b_3となってしまう。
     
  5. b_nを採用する場合、aの方はa_1a_nのどれでも取れるので、maは単にこれまでの最大値を保持するものとする。「a_n \gt maの場合」とか余計な条件は付けずに、ma \times b_nが大きいかどうかを必ず調べても害はない。
  6. 結局、上記2とかは無駄かも・・・とは思いつつも、害はない処理であり、ソースを整える時間ももったいないのでそのまま提出。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;

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

        long ans = 0;
        long ma = 0;
        long mb = 0;
        PrintWriter pw = new PrintWriter(System.out);
        for (int i = 0; i < n; i++) {
            ma = Math.max(ma, a[i]);
            // 思考過程2(このifブロックは消しても通る)
            if (b[i] > mb) {
                mb = b[i];
                ans = ma * mb;
            }
            long val = ma * b[i];
            if (val > ans) {
                mb = b[i];
                ans = ma * mb;
            }
            pw.println(ans);
        }
        pw.flush();
    }
}

ここまで20分40秒+0ペナ。1905位。
486位以上になるためには5~6分程度で通す必要があったので、滅茶苦茶遅い。

B - Mex Boxes

問題

思考過程
  1. 一旦Kを無視して考えると、同じ値のボールは全部別の箱に入れるのが最適。
  2. iのボールがc_i個あるとすると、答えに寄与するボールの数はc_iの累積minを取った形になる。
  3. 答えは、値iが書かれた蓋の数を計算していくとi \times (c_{i-1} - c_i)となるが、よく考えればそもそもボールを1個追加する度に答えは+1+0なので、答えに寄与するボールの数がそのまま答えになる。
  4. 整理すると、c_iの累積minの総和を求めればよい。
     
  5. ここで忘れていたKを思い出す。
  6. c_iKとのminを取る必要がある。 →WA
  7. for文の中でやったせいで、先頭だけminを取れていなかった。むしろ先頭だけmin取れば十分だった。
import java.io.BufferedReader;
import java.io.InputStreamReader;

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

        c[0] = Math.min(c[0], k); // これがなくてWA
        for (int i = 1; i < n; i++) {
            c[i] = Math.min(c[i], c[i - 1]);
            c[i] = Math.min(c[i], k); // 不要
        }

        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans += c[i];
        }
        System.out.println(ans);
    }
}

ここまで30分24秒+1ペナ。1500位。


C - Robot on Grid

問題
ガチャガチャやりやすい例2と、強いケースの例3のおかげで解けた。

思考過程
  1. とりあえず、'R'の場合右のみに、'D'の場合下のみに、'X'の場合両方に、いずれでもない場合3種類を総合して右と下に2倍ずつ遷移するDPを書いてみる。 →例1が3にしかならない。
  2. (2, 1)(2, 2)1通り、(1, 2)(2, 2)2通りになっているが、前者を3通りにして、3+2=5にしたい感じ。
  3. 今見ているマスに関係ない空白マスの数をXとして、3^X倍する感じか? と思うが、例2で遷移の度に3^3倍とかしてたら150なんて大幅に超えてしまっていて何かがおかしい。
     
  4. 例2を色々ガチャガチャやって辻褄を合わせようとする。
  5. まず(1, 1)から(1, 2)(2, 1)へは、無関係の空白マスが3個あるので3^3=27倍、(1, 1)を埋める3通りの内、右へ行くのも下へ行くのも2通りあるのでどちらも2倍し、どちらも54通り。
  6. 空きマスでない部分は通常のDPの通り遷移していくと、下図までは埋まる。
    XD
    DD
    R
    15454
    545454+左からの遷移
    5454+上からの遷移
  7. (3, 2)の「上からの遷移」がいくつになるのか考える。
  8. (2, 2)までの遷移は、(1, 1)が'X'か'R'の2通り、(2, 2), (3, 2), (3, 3)3^3通りで54通りとなっているが、(2, 2)(3, 2)まで行こうとした時点で、(2, 2)は'X'か'D'の2通りとなり、36通りに減りそう。\frac{2}{3}倍?
     
  9. 以下のように当てはめてみたら辻褄が合った。
    3^4=8181 \times \frac{2}{3} = 5454
    81 \times \frac{2}{3} = 545454 + 54 \times \frac{2}{3} = 90
    5454 + 54 \times \frac{2}{3} = 9090 + 90 \times \frac{2}{3} = 150
  10. \frac{2}{3}倍するために、3の逆元を求めておく。
  11. 例3もこれで合ったので提出。
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] sa = br.readLine().split(" ");
        int h = Integer.parseInt(sa[0]);
        int w = Integer.parseInt(sa[1]);
        int k = Integer.parseInt(sa[2]);
        char[][] s = new char[h][w];
        for (int i = 0; i < k; i++) {
            sa = br.readLine().split(" ");
            int a = Integer.parseInt(sa[0]) - 1;
            int b = Integer.parseInt(sa[1]) - 1;
            char c = sa[2].charAt(0);
            s[a][b] = c;
        }
        br.close();

        int mod = 998244353;
        long m3 = modinv(3, mod);
        long[][] dp = new long[h + 1][w + 1];
        dp[0][0] = power(3, h * w - k, mod);
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                if (s[i][j] == 'R') {
                    dp[i][j + 1] += dp[i][j];

                } else if (s[i][j] == 'D') {
                    dp[i + 1][j] += dp[i][j];

                } else if (s[i][j] == 'X') {
                    dp[i][j + 1] += dp[i][j];
                    dp[i + 1][j] += dp[i][j];

                } else {
                    dp[i][j + 1] += dp[i][j] * 2 * m3;
                    dp[i + 1][j] += dp[i][j] * 2 * m3;
                }
                dp[i][j + 1] %= mod;
                dp[i + 1][j] %= mod;
            }
        }
        System.out.println(dp[h - 1][w - 1]);
    }

    // 以下ライブラリ

    static long power(long x, long n, int m) {
        if (n == 0) {
            return 1;
        }
        long val = power(x, n / 2, m);
        val = val * val % m;
        if (n % 2 == 1) {
            val = val * x % m;
        }
        return val;
    }

    static long modinv(long a, int m) {
        long b = m;
        long u = 1;
        long v = 0;
        long tmp = 0;

        while (b > 0) {
            long t = a / b;
            a -= t * b;
            tmp = a;
            a = b;
            b = tmp;

            u -= t * v;
            tmp = u;
            u = v;
            v = tmp;
        }

        u %= m;
        if (u < 0) u += m;
        return u;
    }
}

ここまで55分54秒+1ペナ。508位。
だいぶ盛り返せたが、あと1問解かなければレートプラスにはならない状況。


D - Choosing Up Sides

問題
Eも問題は読んだが全く方針が立ちそうもなかったので、残り時間はほぼDに費した。
だが解けず。

思考過程
  1. _{2^N}C_{2^{N-1}}通り全列挙するだけでは? →TLE
  2. 頭の中が_{N}C_{N-1}になっていていけそうな気がしていたが、全然違った。
     
  3. N=3の場合で考える。
  4. 1をAチーム固定とすると、Aチームは残りの7人中3人。
  5. 7人が同じ回数ずつAチームになる必要があるので、37の最小公倍数が操作回数最小ではないかと当たりを付ける。N=8の場合でも数万行\times 256文字の出力をすればよいのでそれっぽい。
     
  6. あとは条件を満たすように上手く配分できる方法を構築するだけ。これが手作業ならできて、ある程度規則性のある並びにもなっているのだが、簡単に実装できそうにない。できないまま時間切れ。

公式解説を見て、i回目の操作で人jpopcount(i \& j)の偶奇で分けるとあるが、どうやったらこんなのが出てくるのかさっぱり。
popcount(i)=2^{N-1}-1となるiを探してどうにかならないかくらいの発想しかなかった。



終結果:ABCの3完、60分54秒、619位、パフォーマンス1730
レート変動:1863→1850(-13)


A問題が絶望的に遅かった割には浅い傷で済んだのでまあ・・。
Aが6分で解けてBのしょうもないペナルティがなければちょうど42分くらいなので、順当な結果かと思う。