[bzoj1584]打扫卫生

题目大意

把一个长度为n的序列分成若干段。
每一段的代价为其中不同的数的个数的平方。
最小化代价。

DP

设f[i]表示把[1..i]分成若干段的最优解。
那么显然有一个单调性f[i]<=f[i+1]。
我们可以枚举最后一段中不同的数的个数j。那么设b[j]表示最小的k满足[k,i]之间恰好有j个不同的数。
那么显然 f[i]=min[1,i]j=1(f[b[j]1]+j2)
然而这样不行呀。
我们发现,每个数单独一段可以得到答案为n的一个解。
那么也就是说,如果一段中有超过根号n种数字,这绝对不是最优。
因此j的枚举上限可以对根号n取min。
接下来设next[i]表示最大的 j<i 满足a[j]=a[i]。
next数组很好算,只需要一个桶last表示每种数的最后出现位置即可。
那么i每右移一位,b数组如何更新呢?
假若 next[i]>=b[j] ,则不改变b[j]的值。
否则,我们需要不断把b[j]往后移,直至[b[j],i-1]恰好有j-1个不同数。
也就是找到一个最小的k>=b[j]满足last[a[k]]=k(即其是最后一个),那么b[j]=k+1。
这样来调整b数组的话b数组每一位都单调增,一共根号位,所以总复杂度n*根号n。
然后每次f的转移是根号的,总复杂度也为n*根号n。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=40000+10,maxc=200+10;
int next[maxn],f[maxn],a[maxn],b[maxc],last[maxn],s[maxn];
int i,j,k,l,t,n,m,c,top;
int main(){
    freopen("1584.in","r",stdin);freopen("1584.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n) scanf("%d",&a[i]);
    c=floor(sqrt(n));
    fo(i,1,c) b[i]=1;
    fo(i,1,n){
        next[i]=last[a[i]];
        last[a[i]]=i;
        fo(j,1,min(top,c)){
            if (b[j]>next[i]){
                while (1){
                    b[j]++;
                    if (b[j]-1==last[a[b[j]-1]]) break;
                }
            }
        }
        if (!next[i]) top++;
        f[i]=i;
        fo(j,1,min(top,c)) f[i]=min(f[i],f[b[j]-1]+j*j);
    }
    printf("%d\n",f[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值