banner
满五

满五的博客

Non-Binary | High School | OI | Code | Photography | etc. 独立开发者、摄影/文学/电影爱好者 English/中文,日本語を勉強しています INFP | they
github
twitter_id
email

P3147 USACO16OPEN 262144 P 解説

DP シリーズ。

問題#

問題を見ると、Luogu

隣接する同じ数字を結合して、数字を 1 つ増やします。得られる最大値を求めます。

考察#

最初に思いついたのは基本的な区間 DP ですが、説明は省略します:

long long ans = 0;
for(int len = 2; len<=N; len++){
    for(int i = 1; i+len-1<=N; i++){
        int y = i+len-1;
        for(int k = i; k<y; k++){
            if(DP[i][k] == DP[k+1][y]){
                DP[i][y] = max(DP[i][y], DP[i][k] + 1);
            }
        }
        ans = max(ans, DP[i][y]);
    }
}
cout << ans << endl;

しかし、$ 2 \leq n \leq 262144 $ なので、明らかに MLE になります。

最適化#

アイデアを借りて、倍増のような方法を使うことができることに気づきました。

状態 f[i][j] は、合成後の結果が i で右端点が j の区間の左端点の位置を表します。値が 0 の場合は不可能です。

なぜなら、問題は隣接する 2 つの同じ区間を見つけて結合することです。つまり:

f[i][j] = f[i-1][f[i-1][j]];

f[i][j] を i-1 に合成できる2つの区間に分割する

つまり

                 f[i-1][j]
    |------<i-1>-----|----<i-1>-----|
    j                      f[i-1][f[i-1][j]] 

もし f[i-1][j] または f[i-1][f[i-1][j]] が成立しない場合、f[i][j] も成立しないので、0 に遷移します

では、結果をどのように表現しますか?

ans を記録し、f[i][j] が可能な場合は ans を更新します。i が増加するので、max 演算は必要ありません。

次のようになります

for(int i =2; i<=58; i++){
    for(int j = 1; j<=N; j++){
        if(!DP[i][j]){
            DP[i][j] = DP[i-1][DP[i-1][j]];
        }
        if(DP[i][j]){
            ans = i;
        }
    }
}

注意点として、倍増結合を行うため、$log_2 {262144} + 40 = 58$ が可能な最大値です。

初期化#

明らかに結合しない場合も可能なので、入力時に初期化します

for(int i = 1; i<=N; i++){
    int in;
    cin >> in;
    DP[in][i] = i+1;
}

i+1 について:区間の重複を避けるため、f[i][j] は左閉右開区間を表すため、右端点は i+1 です。

まとめ#

この問題は区間 DP の状態の最適化です。DP の学習の道は長く、練習が必要です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。