NoiminのNoise

競技プログラミング (多め) とWeb (たまに) ,自然言語処理 (ブログではまだ)。数式の書き方を一気に KaTeX に変えようとして記事を全削除してインポートし直すなどしたので,過去にブックマークされた記事は URL が変わってしまっている可能性があります…….

みんなのプロコン 2019 F - Pass

F - Pass

問題概要

n人のすぬけ君が1列に並んでいて,両手にボールを持っている.両手のボールの色は青2つ,赤2つ,赤青1つずつのいずれかのである.

以下の操作を2n回繰り返した際にできるボール色の列のパターン数を998244353で割った値を求める.

操作: n人のすぬけ君それぞれが,自分の持つ2つのボールのうち1つを1つ前のすぬけ君に渡す.ただし先頭のすぬけ君は高橋君にボールを渡す.高橋君はもらったボールを順番に並べてボールの列を作る.

解法概要

実は「列のi番目のボールは,前からi番目以内のすぬけ君が持っているボールならどれにでもなりうる」. これはすぬけ君の人数に対する帰納法で証明できる. すぬけ君が0人の時は明らか. すぬけ君が(k-1)人で成り立つと仮定した時,すぬけ君がk人の時は先頭のすぬけ君のボールのうち一方が1番目のボールになる.先頭のすぬけ君のもう片方のボールは(k-1)人のすぬけ君で作った列のうち2番目以降どこにでも挿入できる. よって,

$ dp_{n,r} $= n個ボールを並べていて,そのうち赤いボールはr個のときの並べ方の数

として,DPで計算できる.

DPの初期値は,$ dp_{0,0} = 1 $.0個のものを並べるやり方は1通りなので. DPの遷移は, 先頭(i+1)人のすぬけ君が持つ赤いボールを並べきっていなければ $ dp_{n+1,r+1} += dp_{n,r} $,並べきってしまっていたら$ dp_{n+1,r+1} = 0 $. 同様に,先頭 (i+1) 人のすぬけ君が持つ青いボールを並べきっていなければ$ dp_{n+1,r} += dp_{n,r} $,並べきってしまっていたら$ dp_{n+1,r} = 0 $.

(ちなみに最初はボールがrgbの3色あると誤読していたが,実は2色だった.)

ソースコード

#include <iostream>
#include <string>

using namespace std;

typedef long long ll;

const ll MOD = 998244353LL;

ll dp[4001][4001];

int main() {
    string s;
    cin >> s;
    int n = s.length();

    dp[0][0] = 1LL;
    int red = 0, r = 0;
    for(int i=0;i<2*n;++i) {
        if(i < n) {
            r = 2 - int(s[i] - '0');
            red += r;
        }
        for(int j=0;j<=min(i,red);++j) {
            if(j < red) {
                dp[i+1][j+1] += dp[i][j];
            } else {
                dp[i+1][j+1] = 0;
            }
            dp[i+1][j+1] %= MOD;
            if(i-j < min(i+1,n)*2-red) {
                dp[i+1][j] += dp[i][j];
            } else {
                dp[i+1][j] = 0;
            }
            dp[i+1][j] %= MOD;
        }
    }

    cout << dp[2*n][red] << endl;
}

所感

最近基礎的なDPなら理解できるようになってる気がする.