Hetao P1031 萌萌題 題解 [ 藍 ] [ 線性 dp ]

KS_Fszha發表於2024-10-08

萌萌題:一道結合了觀察性質的線性 dp。

image

觀察

我們先考慮極端情況:所有數相同,所有數降序排列兩種情況。

對於所有數相同的情況,我們發現,最終可以合併出來的區間,最多隻有 \(n \log n\) 個。

怎麼證明?考慮固定右端點,那麼我們想要合併出一個點,就得選 \(2^k\) 個數出來,這就有 \(\log n\) 次選擇方式。總的來說就是有 \(n \log n\) 種選數方式了。

所有數降序排列是多少?我們只需要將最後兩個元素合併,然後繼續往前面合併即可。總體來說有 \(n\) 個。

dp 設計

這題還有個很重要的觀察:對於固定了右端點的時候,假設我們要讓這個數增加 \(v\),那麼合併的方案(區間)一定是唯一的。這就啟發了我們可以設計一個 dp,定義 \(dp_{i,j}\) 表示讓元素 \(i\) 增加 \(j\) 後,合併到的區間的左端點的前一個數是哪裡。

為什麼要前一個數?其實你不要這前一個數其實也可以做。只不過我這樣設計來講比較好寫轉移的程式碼。

接下來就是很顯然的轉移,假設目前合併到的區間的左端點的前一個數為 \(now\),要增加 \(v\)

\[dp_{i,v}=dp_{now,a_{now}-(a_i+v-1)} \]

然後記錄一下合法狀態就好了。

這樣寫對嗎?實際上假了。這是我賽時的做法,當時誤以為一個數最多增量只可能是 \(\log n\)。實際上降序排列就能把增量卡到 \(n\)

那麼開大小為 \(n\) 的增量可不可以?直接開肯定不行,會 MLE,但是動態開不就行了?根據前面的觀察,我們最多隻有 \(n \log n\) 個合法區間,那麼每個數的增量也最多隻有 \(n \log n\) 個,我們用 vector 模擬動態開 dp 陣列的過程,這道題就 AC 了。

注意,當某個區間無法繼續合併下去的時候,要立刻 break,才能保證複雜度正確,否則會退化為平方級別。

時間複雜度為 \(O(n\log n)\)

程式碼

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
int n,a[100005],ans=0;
vector<int>dp[100005];
int main()
{
    freopen("cute.in","r",stdin);
    freopen("cute.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        dp[i].emplace_back(i-1);
        for(int j=1;j<=100000;j++)
        {
            int now=dp[i][j-1];
            int ta=a[now];
            int dt=a[i]+j-1-ta;
            if(dt>=0&&dt<dp[now].size())dp[i].emplace_back(dp[now][dt]);
            else break;
        }
    }
    for(int i=1;i<=n;i++)ans=max(ans,a[i]+int(dp[i].size())-1);
    cout<<ans<<endl;
    return 0;
}

相關文章