给你一个序列,让你划分成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\n",f[n][m]);return 0;
}