2023年10月25日 模擬試験の反省

時間管理

7:40 から 8:40 まで、対拍と乱数生成の準備を行い、問題を読み込み、T1 を開始しました。40分で初回の提出を完了し、対拍で小さな誤りを見つけたため修正して再提出しました。

8:40 から 10:20 まで、T2 の解決に取り組みました。以前の模擬試験の T2 と類似点がありましたが、直接的な解法が見つからなかったため、まずは暴力解法を考えました。しかし、n ≤ 6 の場合も難しく、一部のケースは連鎖状態でしたが、適切な解法を見つけることができませんでした。その後、T3 に移りました。

10:20 から 11:10 まで、T3 の部分点について考えました。いくつかの特殊な性質を利用して解く方法を考えましたが、具体的な実装に苦労しました。dp (動的計画法) を用いて解こうとしたものの、初期のアプローチには問題があり、最終的に O(nm) の解法を完成させました。

11:10 から 11:40 まで、T4 の暴力解法を試みましたが、時間内に完成させることが難しく、結果として T4 では得点できませんでした。

試験結果

100 + 0 + 35 + 0 = 135 ポイントで、順位は 3 位でした。

試験の反省

T2 に多くの時間を費やしたため、T4 の暴力解法を書く時間が不足しました。今後は、長時間進展しない問題は他の問題に切り替えるべきです。

問題解説

T1

貪欲法を使用し、各ステップで最も大きい数を削除します。この戦略が最適であることを証明できます。連結リストとセグメントツリーを使って実装できます。

T2

前の問題(多校連携テスト Day3 T2)と類似していますが、異なる点は「すべての子の番号が i より小さい」ではなく、「少なくとも一つの子の番号が i より大きい」という条件です。状態 dp[i][j][k] を設定し、i 番目のノードまで考慮し、j 本の木があり、k 個の空き位置がある場合の解の数を表します。


for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= i; j++) {
        for (int k = 0; k <= min(n, 2 * i); k++) {
            int p = dp[i][j][k];
            for (int h = l[i + 1]; h <= r[i + 1]; h++) {
                if (h == 0) {
                    dp[i + 1][j + 1][k + h] = (dp[i + 1][j + 1][k + h] + p) % mod;
                    if (k) dp[i + 1][j][k - 1] = (dp[i + 1][j][k - 1] + (1LL * k * p) % mod) % mod;
                }
                if (h == 1) {
                    dp[i + 1][j + 1][k + h] = (dp[i + 1][j + 1][k + h] + (2LL * p) % mod) % mod;
                    dp[i + 1][j][k - 1 + h] = (dp[i + 1][j][k - 1 + h] + (2LL * k * p) % mod) % mod;
                }
                if (h == 2) {
                    dp[i + 1][j + 1][k + h] = (dp[i + 1][j + 1][k + h] + p) % mod;
                    dp[i + 1][j][k - 1 + h] = (dp[i + 1][j][k - 1 + h] + (1LL * k * p) % mod) % mod;
                    dp[i + 1][j][k + 1] = (dp[i + 1][j][k + 1] + (2LL * j * p) % mod) % mod;
                    if (j > 1) dp[i + 1][j - 1][k] = ((1LL * (1LL * p * k) % mod * (j - 1) * 2) % mod + dp[i + 1][j - 1][k]) % mod;
                }
            }
        }
    }
}

T3

n ≤ 2000 に対して、dp[i][j] を設定し、S 文字列の i 番目の文字まで考慮し、T 文字列とのマッチング長が j である最小コストを表します。初期化は非常に大きな値で、dp[0][0] は 0 です。各文字に対して、残すか削除するかの選択肢があります。s[i] が t[j] と一致する場合は残すことができます。また、t において s[i] と同じ文字が存在しない場合、s[i] を削除することができます。


LL sol() {
    memset(pre, 0, sizeof pre), memset(bac, 0, sizeof bac);
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= 26; j++) pre[i][j] = pre[i - 1][j];
        pre[i][(int)(t[i] - 'a' + 1)]++;
    }
    for (int i = m; i >= 1; i--) {
        for (int j = 1; j <= 26; j++) bac[i][j] = bac[i + 1][j];
        bac[i][(int)(t[i] - 'a' + 1)]++;
    }
    memset(dp, 0x7f, sizeof dp);
    dp[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= min(i, m); j++) dp[i & 1][j] = 0x7f7f7f7f7f7f7f7f;
        for (int j = 0; j <= min(i, m); j++) {
            if (!j) dp[i & 1][j] = dp[(i - 1) & 1][j] + 1LL * w[i];
            if (s[i] == t[j]) dp[i & 1][j] = min(dp[i & 1][j], dp[(i - 1) & 1][j - 1]);
            if (!pre[j][(int)(s[i] - 'a' + 1)] || !bac[j + 1][(int)(s[i] - 'a' + 1)]) dp[i & 1][j] = min(dp[i & 1][j], dp[(i - 1) & 1][j] + 1LL * w[i]);
        }
    }
    return (dp[n & 1][m] == 0x7f7f7f7f7f7f7f7f ? -1 : dp[n & 1][m]);
}

T4

まだ解法を確立していません。

タグ: 競技プログラミング 動的計画法 貪欲法 セグメントツリー 文字列処理

7月14日 20:38 投稿