banner
满五

满五的博客

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

P3147 USACO16OPEN 262144 P 解題

DP 系列。

题面#

看题,Luogu

合併相鄰的相同數字,變成數字加一。求獲得的最大值。

思考#

最初想到的是基礎的區間 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 即 不可行。

因為題目要找兩個相鄰相等的區間,合成。有:

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

把 f[i][j] 拆分成兩個能合成為 i-1 的區間

                 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 學習之路漫漫,還需要多加練習。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。