给你一个序列,让你划分成K段,每段的价值是其内部权值的种类数,让你最大化所有段的价值之和。
裸dp
f(i,j)=max{f(k,j-1)+w(k+1,i)}(0<=k<i)
先枚举j,然后枚举i的时候,用线段树进行优化,对a(i)上一次出现的位置到i之间的f(k,j-1)的答案进行+1,然后求个i的前缀max。
要注意线段树区间加的时候其实要包含上0。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define lson rt<<1,l,m #define rson rt<<1|1,m+1,r int n,m,a[35010],f[35010][60]; int maxv[35010<<2]; int delta[35010<<2]; void pushdown(int rt)//将rt结点的懒惰标记下传 { if(delta[rt]) { delta[rt<<1]+=delta[rt];//标记下传到左结点 delta[rt<<1|1]+=delta[rt];//标记下传到右结点 maxv[rt<<1]+=delta[rt]; maxv[rt<<1|1]+=delta[rt]; delta[rt]=0; } } void update(int ql,int qr,int v,int rt,int l,int r) { if(ql<=l&&r<=qr) { delta[rt]+=v;//更新当前结点的标记值 maxv[rt]+=v; return ; } pushdown(rt);//将该节点的标记下传到孩子们 int m=(l+r)>>1; if(ql<=m) update(ql,qr,v,lson); if(m<qr) update(ql,qr,v,rson); maxv[rt]=max(maxv[rt<<1],maxv[rt<<1|1]); } int query(int ql,int qr,int rt,int l,int r) { if(ql<=l&&r<=qr) return maxv[rt]; pushdown(rt);//将该节点的标记下传到孩子们 int m=(l+r)>>1; int res=-2147483647; if(ql<=m) res=max(res,query(ql,qr,lson)); if(m<qr) res=max(res,query(ql,qr,rson)); return res; } int now[35010],last[35010]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ scanf("%d",&a[i]); } for(int i=1;i<=n;++i){ last[i]=now[a[i]]; now[a[i]]=i; } for(int j=1;j<=m;++j){ if(j!=1){ memset(maxv,0,sizeof(maxv)); memset(delta,0,sizeof(delta)); for(int i=j-1;i<=n;++i){ update(i,i,f[i][j-1],1,0,n); } } update(max(last[j],j-1),j-1,1,1,0,n); f[j][j]=j; for(int i=j+1;i<=n;++i){ update(max(last[i],j-1),i-1,1,1,0,n); f[i][j]=query(j-1,i-1,1,0,n); } } printf("%d ",f[n][m]); return 0; }