P2943 [USACO09MAR]Cleaning Up G
Solution
这种题很显然能想到用区间DP去做。
那么我们先列一个最朴素的方程: \(f_{i}=\min(f_{i},f_{j}+s)\) ,其中 \(s\) 是 \(i\) ~ \(j\) 的不同种类数。
这是 \(O(n^2)\) 的显然不太可。
那有什么能优化的?
发现如果将每只奶牛单独作为一段的话,最后的答案是 \(n\) 。
所以 \(s\) 中的种类数应该小于 \(\sqrt n\) ,不然代价就超过 \(n\) 了。
我们拿一个数组 \(pos\) ,表示段内有 \(i\) 个数,其中右端点为当前点,左端点为 \(pos_i\) ,那么现在的转移方程就是:
\[f_{i=}\min_{j=1}^{\sqrt n}\{f_{pos_j-1}+j^2,f_{i}\}
\]
考虑如何维护 \(pos\) ԅ(¯﹃¯ԅ)
可以拿 \(\sqrt n\) 个桶,然后对当前位即 \(P_i\) 进行判断,如果是新的值,加入桶,判断从 \(pos_j\) 到 \(i\) 是否是 \(j\) 个数,要是大于,就及时往右移,直到满足条件。
Code
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=4e4+10,B=210;
int n,m,a[B][N],cnt[B],ans,f[N],P[N],pos[B];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
int main(){
n=read(); m=read();
for(int i=1;i<=n;i++) P[i]=read();
memset(f,0x3f3f,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<B;j++){
++a[j][P[i]];
if(a[j][P[i]]==1){
++cnt[j];
if(cnt[j]>j){
while(--a[j][P[pos[j]]]!=0) ++pos[j];
++pos[j];
cnt[j]=j;
}
}
if(cnt[j]==j) f[i]=min(f[i],f[pos[j]-1]+j*j);
}
printf("%d\n",f[n]);
return 0;
}