AtCoder Grand Contest 002:B - Box and Ball

問題

https://atcoder.jp/contests/agc002/tasks/agc002_b

考え方

実験??実験のコツがわかるようになりたい...。

赤いボールが入っている箱から M 回の操作によって到達できる可能性がある箱については赤いボールが入りうる。ただし操作後に操作元の箱 x が空になる場合はその箱には赤いボールが入っていることはない。

これをシミュレーションすればよい。

Submission #4351818 - AtCoder Grand Contest 002

実験用コード

到達可能性について、editorialにように言われればまぁ理解できるが、なかなかぱっと正当性がわからない...。ちなみに以下が実験したコードというかぱっと思いついたdp解...

       public void solve(int testNumber, MyInput in, PrintWriter out) {

            int n = in.nextInt(), m = in.nextInt();

            // i 回目の操作で箱 j に赤いボールが r 個、白いボールが b 個入っている可能性があるかどうか
            boolean[][][][] dp = new boolean[m+1][n][n+1][n+1];
            for (int i = 0; i < n; i++) {
                dp[0][i][i == 0 ? 1 : 0][i != 0 ? 1 : 0] = true;
            }

            for (int i = 0; i < m; i++) {
                int x = in.nextInt()-1, y = in.nextInt()-1;

                // 赤いボールと白いボールがある場合
                boolean done = false;
                for (int r = 1; r <= n; r++) {
                    for (int w = 1; w <= n; w++) {
                        if (dp[i][x][r][w]) {

                            // 赤いボールを渡す場合
                            for (int r2 = 0; r2 <= n; r2++) {
                                for (int w2 = 0; w2 <= n; w2++) {
                                    if (dp[i][y][r2][w2]) {
                                        dp[i+1][y][r2+1][w2] = true;
                                        dp[i][x][r][w] = false;
                                        dp[i+1][x][r-1][w] = true;
                                    }
                                }
                            }

                            // 白いボールを渡す場合
                            for (int r2 = 0; r2 <= n; r2++) {
                                for (int w2 = 0; w2 <= n; w2++) {
                                    if (dp[i][y][r2][w2]) {
                                        dp[i+1][y][r2][w2+1] = true;
                                        dp[i][x][r][w] = false;
                                        dp[i+1][x][r][w-1] = true;
                                    }
                                }
                            }

                            done = true;
                        }
                    }
                }

                // 赤いボールしかない場合
                if (!done) {
                    for (int r = 1; r <= n; r++) {
                        if (dp[i][x][r][0]) {

                            // 赤いボールを渡す場合
                            for (int r2 = 0; r2 <= n; r2++) {
                                for (int w2 = 0; w2 <= n; w2++) {
                                    if (dp[i][y][r2][w2]) {
                                        dp[i+1][y][r2+1][w2] = true;
                                        dp[i][x][r][0] = false;
                                        dp[i+1][x][r-1][0] = true;
                                    }
                                }
                            }

                            done = true;
                        }
                    }
                }

                // 白いボールしかない場合
                if (!done) {
                    for (int w = 1; w <= n; w++) {
                        if (dp[i][x][0][w]) {

                            // 白いボールを渡す場合
                            for (int r2 = 0; r2 <= n; r2++) {
                                for (int w2 = 0; w2 <= n; w2++) {
                                    if (dp[i][y][r2][w2]) {
                                        dp[i+1][y][r2][w2+1] = true;
                                        dp[i][x][0][w] = false;
                                        dp[i+1][x][0][w-1] = true;
                                    }
                                }
                            }
                        }
                    }
                }

                if (!done) new RuntimeException();

                // 直前の状態のマージ(移動しなかった分)
                for (int j = 0; j < n; j++) {
                    if (j == y) continue;
                    for (int r = 0; r <= n; r++) {
                        for (int w = 0; w <= n; w++) {
                            dp[i+1][j][r][w] |= dp[i][j][r][w];
                        }
                    }
                }
            }

            // m回操作後の最終的な状態で赤いボールが入っている可能性がある箱の数を数える
            int ans = 0;
            for (int i = 0; i < n; i++) {
                boolean ok = false;
             top:
                for (int r = 1; r <= n; r++) {
                    for (int w = 0; w <= n; w++) {
                        if (dp[m][i][r][w]) {
                            ok = true;
                            break top;
                        }
                    }
                }
                if (ok) ans++;
            }
            out.println(ans);
        }

どこに着目して考察するべきだったか

実験をすると分かる

何がバグっていたか

得た知見(典型ポイント)

  • 実験して法則を見出す
  • シミュレーション

類題