CF868F Yet Another Minimization Problem(动态规划,决策单调性,分治)

设fi,j为前i个数分j段的最小花费,wl,r为[l,r]全在一段的费用。

fi,j=mink=1i{fk,j−1+wk,i}

显然jj这一维可以滚掉,于是变成gi=mink=1i{fk+wk,i}做m遍(题目中的k)

这又是一个决策单调性优化的式子。

再次推广蒟蒻的DP优化总结

求解区间:|←预处理→| l ↓ mid r

决策区间:L ↓k R


设当前的求解区间为[l,r][l,r],最优决策区间[L,R]。对于当前分治的中点mid,我们需要在[L,min(R,mid)]中暴力找到最优决策kk。注意到从wl,r到wl,r+1或者从wl,r到wl+1,r都是可以做到O(1)的,只要开一个桶记录当前区间每个颜色出现次数就可以啦。把指针i从L移到min(R,mid)并不断的算fi+wi,mid,最终可以找到k。

注意一点,当进入求解区间时,我们的应该要确保[L,l−1]的信息的存在,这样才能保证分治的复杂度。

于是我们考虑进子问题之前如何先处理出子问题的答案。先看左边的子问题([l,mid−1],[L,k])显然和当前问题的[L,l−1]是一样的。注意到我们在求kk的时候对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;
}

猜你喜欢

转载自blog.csdn.net/c_czl/article/details/86673462