1. 程式人生 > >【學術篇】CF833B TheBakery 分治dp+主席樹

【學術篇】CF833B TheBakery 分治dp+主席樹

題目大意: 將\(n\)個蛋糕分成恰好\(k\)份, 求每份中包含的蛋糕的種類數之和的最大值.

這題有兩種做法. 第一種是線段樹優化dp, 我還沒有考慮. 另一種就是分治+主席樹.
然後如果看到分治+主席樹的話 可以看成是題的二合一~
不過ADAMOLD正解應該是有\(O(nk)\)做法的吧, 我的\(O(nklogn)\)分治好像被卡了一點常數QwQ
首先我們可以非常容易的看出這題要用dp和狀要用到的狀態轉移方程
\[ f[i][j]=max{f[i-1][k]+d(k+1,j)}\ (k\in[1,j)) \]
那麼很顯然我們已經可以\(O(n^2k)\)做了. 但是這顯然過不去, 我們必須要優化.
優化的方式有兩種, 就是上面提到的線段樹或者分治

.

分治的話就是非常套路的東西了, 專門用來應對\(f[i][j]=f[i-1][k]+w(i,j)\), 其中費用函式\(w\)沒啥特殊性質的情況的dp.
通常情況下, 我們觀察每次要轉移\(f[i][j]\)時令轉移最優的\(A[i][j]\), 會發現有
\[ A[i, j - 1] \leq A[i, j]\leq A[i + 1, j] \]
啥意思呢就是這破玩意是單調的.所以我們假如我們求出\(f[i][mid]\)要在\(k\)處轉移, 我們就知道\(f[i][1..mid-1]\)的轉移點是在\((1,k)\)的了.
這樣我們就有了一個分治的形式, 就可以直接做了. 而這類分治dp是有套路的(又到了py式

虛擬碼時間

def solve(x,l,r,L,R): # 處理f[x][l]..f[x][r]這一堆的dp值, 轉移點落在[L,R]
    if l<=0 or l>r or r<=0 or r>n: # xjb寫一通反正就是如果越界就不處理了
         return
    if x==1: # f[1]的情況作為邊界條件顯然要特殊處理.
        for i  in range(l,r+1):
            f[x][i]=w(i,1)
    f[x][i]=INF # 這裡的INF表示反向極限值(就是你要求min的話就是最大值
    g[x][i]=L
    for i in range(L,R+1):
        if f[x-1][i]+w(i+1,mid)>f[x][mid]: # 自然是對f[x][mid]進行轉移啦~
            f[x][mid]=f[x-1][i]+w(i+1,mid) # 注意這裡的i+1如果>mid的話要返回非法值(比如INF
            g[x][mid]=i # 標記最優的轉移位置供繼續分治使用
    solve(x,l,mid-1,L,g[x][mid]) # 遞迴處理左半邊
    solve(x,mid+1,r,g[x][mid],R) # 遞迴處理右半邊

for i in range(1,k+1): # 第一維1~k都要做一遍..
    solve(i,1,n,1,n)

就可以啦, 每個題的區別就在求w(i,j)的部分了.
可以證明, 這個分治的過程每層是\(O(nlogn)\)(反正窩不會證), 從1~k各掃一遍就是\(O(nklogn)\)的了.
對於ADAMOLD來說, 自然用\(O(n^2)\)預處理二維字首和搞一下就ok了. (但是\(O(nklogn)\)有點卡常數?!

然後這個題的w(i,j)就是表示[i,j]區間內的蛋糕的種類數.
那麼靜態詢問區間種類數的話我們就可以去看下DQUERY這道題咯(明顯的模板題)
由於我並不認為這題可以離線, 所以樹狀陣列或莫隊是簡明不行的. 我們要用主席樹.

我們對每個時刻開一個\(n\)個節點的線段樹, 然後用一個map記錄每個數上一次出現的位置.
如果在第\(i\)個位置遇到一個沒出現過的數\(x\), 我們把第\(i\)棵樹的\(i\)位置+1.
如果遇到一個出現過的數\(y\), 我們先在第\(i\)棵樹上把它上一次出現的位置\(last_y\)-1, 然後\(i\)位置+1,
這樣就可以保證每個重複的數只存在於最後一次出現的位置,
這時候第\(i\)棵線段樹就表示第\(i\)個時刻每個位置上不同的數的個數了.
我們可以畫圖來更好的體會這一點, 以DQUERY的樣例為例,

5
1 1 2 1 3

首先我們要建一個有\(n\)個葉子節點的線段樹(啊啊啊我畫的圖好醜啊 大家湊合看看, 意會一下?

我們在1位置遇到了一個1, 1還從來沒有出現過, 我們讓1 +1, 然後把\(last_1\)設成1.

我們在2位置又遇到了一個1, 1出現過了, 我們讓\(last_1\)(1) -1, 然後讓2 +1, 把\(last_1\)設成2.

我們在3位置遇到一個2, 2沒出現過, 3 +1, \(last_2=3\)

我們在4位置又雙叒叕遇到一個1, \(last_1\)(2) -1, 4 +1,\(last_1=4\)

5位置遇到一個3, 5 +1, \(last_3=5\)

這樣我們就建好靜態的主席樹了, 我們看一下, 是不是第\(i\)棵樹對應著第\(i\)個時刻的種類情況呢~
這樣我們查詢\([L,R]\)這個區間的時候, 就只需要在第\(R\)棵樹上查\(L\)點及以後的和就完了OwO
單次查詢都是\(O(logn)\)

然後我們就解決了分治時候算\(w(i,j)\)的問題, 現在的時間複雜度應該是\(O(nklogn)*O(logn)=O(nklog^2n)\)的.
可以通過本題了, 雖然複雜度比線段樹優化的\(O(nklogn)\)要多個log, 但是實際情況並沒有慢太多(都是樸素實現大約慢一倍?
不過(可能?)要好寫一點..

程式碼:

#include <cstdio>
#include <unordered_map>
std::unordered_map<int, int> mp;
const int N=36005;
struct node{int sum,l,r;}t[N<<5];
int f[55][N],g[55][N],rt[N],n,k,tot;
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar()) a=a*10+c-48;
return a;}
void update(int &x,int l,int r,int pre,int pos,int val){
    t[++tot]=t[pre]; x=tot; t[x].sum+=val;
    if(l==r) return; int mid=(l+r)>>1;
    if(pos<=mid) ::update(t[x].l, l, mid, t[pre].l, pos, val);
    else ::update(t[x].r, mid+1, r, t[pre].r, pos, val);
}
int query(int x,int l,int r,int L){
    if(L<=l) return t[x].sum;
    int mid=(l+r)>>1;
    if(L<=mid) return ::query(t[x].l, l, mid, L)+t[t[x].r].sum;
    return ::query(t[x].r, mid+1, r, L);
}
inline int qquery(int l,int r){
    if(l>r) return 0x7fffffff;
    return ::query(rt[r], 1, n, l);
}
void solve(int x,int l,int r,int L,int R){
    if(l<=0||l>r||r<=0||r>n) return;
    if(x==1){
        for(int i=l;i<=r;++i)
            f[x][i]=::qquery(1, i);
        return;
    } int mid=(l+r)>>1,maxn=0;
    f[x][mid]=0;; g[x][mid]=L;
    for(int i=L;i<=R;++i){
        maxn=f[x-1][i]+::qquery(i+1, mid);
        if(maxn>f[x][mid])
            f[x][mid]=maxn,g[x][mid]=i;
    }
    solve(x,l,mid-1,L,g[x][mid]);
    solve(x,mid+1,r,g[x][mid],R);
}
int main(){ n=gn(); k=gn(); int tmp;
    for(int i=1;i<=n;++i){
        int x=gn();
        if(mp.find(x)==mp.end())
            ::update(rt[i], 1, n, rt[i-1], i, 1);
        else{
            ::update(tmp, 1, n, rt[i-1], mp[x], -1);
            ::update(rt[i], 1, n, tmp, i, 1);
        }
        mp[x]=i;
    }
    for(int i=1;i<=k;++i)
        ::solve(i, 1, n, 1, n);
    printf("%d",f[k][n]);
}

對 就是這樣咯~