[bzoj1584]打扫卫生

来源:互联网 发布:java源代码阅读工具 编辑:程序博客网 时间:2024/04/29 09:57

题目大意

把一个长度为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]);}
0 0
原创粉丝点击