洛谷CF868F Yet Another Minimization Problem(動態規劃,決策單調性,分治)

洛谷題目傳送門html

貌似作全部的DP題都要先搞出暴力式子,再往正解上靠。。。優化

\(f_{i,j}\)爲前\(i\)個數分\(j\)段的最小花費,\(w_{l,r}\)\([l,r]\)全在一段的費用。spa

\[f_{i,j}=\min\limits_{k=1}^{i}\{f_{k,j-1}+w_{k,i}\}\]指針

顯然\(j\)這一維能夠滾掉,因而變成\(g_i=\min\limits_{k=1}^{i}\{f_k+w_{k,i}\}\)\(m\)遍(題目中的\(k\)code

這又是一個決策單調性優化的式子。仍是決策二分棧嗎?要不得了,由於就算知道\(i,k\)也無法直接算\(f_k+w_{k,i}\)htm

再次推廣蒟蒻的DP優化總結blog

分治。總結裏的概述蒟蒻也懶得再擓一遍了。。。就說說這題的實現細節吧。get


\(L^AT_EX\)畫圖?(霧string

求解區間:\(|\gets\)預處理\(\to|\) \(l\frac{\qquad\qquad\qquad\downarrow^{mid}\qquad\qquad\qquad}{}r\)flash

決策區間:\(L\frac{\qquad\qquad\qquad\qquad\downarrow^{k}\qquad\qquad\qquad}{}R\)


設當前的求解區間爲\([l,r]\),最優決策區間\([L,R]\)。對於當前分治的中點\(mid\),咱們須要在\([L,\min(R,mid)]\)中暴力找到最優決策\(k\)。注意到從\(w_{l,r}\)\(w_{l,r+1}\)或者從\(w_{l,r}\)\(w_{l+1,r}\)都是能夠作到\(O(1)\)的,只要開一個桶記錄當前區間每一個顏色出現次數就能夠啦。把指針\(i\)\(L\)移到\(\min(R,mid)\)並不斷的算\(f_i+w_{i,mid}\),最終能夠找到\(k\)

注意一點,當進入求解區間時,咱們的應該要確保\([L,l-1]\)的信息的存在,這樣才能保證分治的複雜度。

因而咱們考慮進子問題以前如何先處理出子問題的答案。先看左邊的子問題(\([l,mid-1],[L,k]\))顯然和當前問題的\([L,l-1]\)是同樣的。注意到咱們在求\(k\)的時候對\(w\)和桶都作了修改,那麼咱們直接還原回來就能夠進左子問題了。

而右子問題呢?(\([mid+1,r],[k,R]\))它要預處理的是\([k,mid]\),而當前的是\([L,l-1]\)。因此咱們先把右端點指針從\(l-1\)移到\(mid\),桶和\(w\)都加上去,再把左端點從\(L\)移到\(k-1\),桶和\(w\)都減掉,接着進去就行了。回溯的時候仍是要還原到\([L,l-1]\),由於上一層要接着用。

注意答案是long long級別的。

代碼通過了精心排版(尤爲是分治那一塊)

#include<cstdio>
#include<cstring>
#define RG register
#define R RG int
#define G c=getchar()
typedef long long LL;
const int N=1e5+9;
int a[N],c[N];
LL ff[N],gg[N],*f=ff,*g=gg;
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x=x*10+(c&15),G;
    return x;
}
void solve(R l,R r,R kl,R kr,RG LL w){//kl,kr就是決策區間
    if(l>r)return;//邊界
    R m=(l+r)>>1,k=0,p=m<kr?m:kr,i;
    for(i= l;i<=m;++i)w+=c[a[i]]++;//求k
    for(i=kl;i<=p;++i)w-=--c[a[i]],g[m]>f[i]+w?g[m]=f[i]+w,k=i:0;
    for(i=kl;i<=p;++i)w+=c[a[i]]++;//還原
    for(i= l;i<=m;++i)w-=--c[a[i]];
    solve(l,m-1,kl,k,w);
    for(i= l;i<=m;++i)w+=c[a[i]]++;//調整
    for(i=kl;i< k;++i)w-=--c[a[i]];
    solve(m+1,r,k,kr,w);
    for(i=kl;i< k;++i)++c[a[i]];//再次還原
    for(i= l;i<=m;++i)--c[a[i]];
}
int main(){
    R n=in(),k=in();
    RG LL*tmp;
    for(R i=1;i<=n;++i)//第一次直接算
        f[i]=f[i-1]+c[a[i]=in()]++;
    memset(c,0,(n+1)<<2);
    while(--k){
        memset(g,1,(n+1)<<3);
        solve(1,n,1,n,0);
        tmp=f;f=g;g=tmp;
    }
    printf("%lld\n",f[n]);
    return 0;
}
相關文章
相關標籤/搜索