思路:讲真,这题难度有点大,看第一眼很容易想出O(k*n^2)的dp方法。如果想不出来那么你dp可能还没入门。。。dp[i][j]第i个当前末尾为i节点已经分成了j块,那么有动态转移方程:dp[i][j]=max(dp[i][j],dp[x][j-1]+val[x+1][i]),0<x<i,val表示区间权值,那么再进一步想想怎么优化,因为我们要求最大值,所以我们可以考虑用线段树维护最大值,每次扫过一个位置只有一段区间的val会+1,所以每次扫过一个点更新一下线段树即可,因为有k块,所以要建k个线段树,本来我想敲的了,然后我又怕超内存,而且我也懒,又想了一下,因为每次都需要知道当前块数-1的信息,所以先枚举块数后枚举位置来递推,通过滚动每次只需建一个线段树,不过实际总的来说还是建了k棵,不过是用了1棵树的内存,大概就是这样,下面给代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
//const LL MOD=1e9+7;
#define inf 0x3f3f3f3f
#define lowerbit(x) x&(-x)
#define maxn 35005
int dp[maxn];
int maxnum[maxn<<2],mark[maxn<<2],Left[maxn],a[maxn],vis[maxn];
void build(int l,int r,int now){
mark[now]=0;
if(l==r){
maxnum[now]=dp[l];
return;
}
int mid=l+r>>1;
build(l,mid,now<<1);
build(mid+1,r,now<<1|1);
maxnum[now]=max(maxnum[now<<1],maxnum[now<<1|1]);
}
void lazymark(int now){
maxnum[now << 1] += mark[now];
mark[now << 1] += mark[now];
maxnum[now << 1 | 1] += mark[now];
mark[now << 1 | 1] += mark[now];
mark[now] = 0;
}
void update(int l1, int r1, int l2, int r2, int now){
if (l2 <= l1&&r2 >= r1){
maxnum[now] += 1;
mark[now]++;
return;
}
int mid = l1 + r1 >> 1;
if (mark[now])
lazymark(now);
if (l2 <= mid)
update(l1, mid, l2, r2, now << 1);
if (r2 > mid)
update(mid + 1, r1, l2, r2, now << 1 | 1);
maxnum[now] = max(maxnum[now << 1], maxnum[now << 1 | 1]);
}
int query(int l1,int r1,int l2,int r2,int now){
if (l2 <= l1&&r2 >= r1)
return maxnum[now];
int mid = l1 + r1 >> 1;
if (mark[now])
lazymark(now);
int a=0,b=0;
if(l2<=mid)
a=query(l1,mid,l2,r2,now<<1);
if(r2>mid)
b=query(mid+1,r1,l2,r2,now<<1|1);
return max(a,b);
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++){
scanf("%d", &a[i]);
Left[i] = vis[a[i]];
vis[a[i]] = i;
}
for(int i=1;i<=k;i++){
build(0,n,1);
for(int j=i;j<=n;j++){
update(0,n,Left[j],j-1,1);
dp[j]=query(0,n,0,j-1,1);
}
}
printf("%d\n",dp[n]);
}