給你一個序列,讓你劃分成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;
}