题面
题意
给你n个数,将它们分成k组(不改变顺序),每组的价值为这组中数字的种数,求所有组的最大价值和。
做法
首先思考最朴素的dp: dp[i][j]表示将前i个数分成j组的最大价值和。 因此,dp[i][j]=max(dp[i-k][j-1]+val[i-k+1][i]). 时间复杂度为O(k*n^2). 考虑优化这个dp,可以先求出dp[i][1](这列数的前缀的价值),然后求出dp[i][2],dp[i][3]……dp[i][k],具体求dp[i][j]值的时候,可以将dp[i][j-1]的值存入线段树,逐个数考虑其贡献,发现每个数对答案做出的贡献可以转化为区间加,每次求出最大值即可。
代码
#include<iostream>
#include<cstdio>
#define
mid ((l+r)>>
1)
#define N
35010
using namespace std;
int n,k,num[N],last[N],pos[N],dp[N][
60],tt=
1,tmp;
struct Node
{
int ls,rs,mx,sum;
} node[N<<
1];
void build(
int now,
int l,
int r)
{
if(l==r) return;
if(l<=
mid)
{
node[
now].ls=++tt;
build(tt,l,
mid);
}
if(
mid<r)
{
node[
now].rs=++tt;
build(tt,
mid+
1,r);
}
}
void add(
int now,
int l,
int r,
int u,
int v,
int w)
{
if(l==u&&v==r)
{
node[
now].sum+=w;
return;
}
if(u<=
mid)
{
add(node[
now].ls,l,
mid,u,min(
mid,v),w);
}
if(
mid<v)
{
add(node[
now].rs,
mid+
1,r,max(
mid+
1,u),v,w);
}
node[
now].mx=max(node[node[
now].ls].mx+node[node[
now].ls].sum,node[node[
now].rs].mx+node[node[
now].rs].sum);
}
void ql(
int now,
int l,
int r,
int u)
{
node[
now].sum=node[
now].mx=
0;
if(l==r) return;
if(l<=
mid)
{
ql(node[
now].ls,l,
mid,u);
}
if(
mid<r)
{
ql(node[
now].rs,
mid+
1,r,u);
}
}
void chg(
int now,
int l,
int r,
int u,
int v)
{
if(l==r)
{
node[
now].mx=v;
return;
}
int res=
0;
if(u<=
mid)
{
chg(node[
now].ls,l,
mid,u,v);
}
else
{
chg(node[
now].rs,
mid+
1,r,u,v);
}
node[
now].mx=max(node[node[
now].ls].mx+node[node[
now].ls].sum,node[node[
now].rs].mx+node[node[
now].rs].sum);
}
int main()
{
int i,j;
cin>>n>>k;
for(i=
1; i<=n; i++)
{
scanf(
"%d",&num[i]);
if(!pos[num[i]]) tmp++;
last[i]=pos[num[i]];
pos[num[i]]=i;
dp[i][
1]=tmp;
}
build(
1,
1,n);
for(i=
2; i<=k; i++)
{
ql(
1,
1,n,i-
1);
for(j=
1;j<i;j++) chg(
1,
1,n,j,dp[j][i-
1]);
for(j=i; j<=n; j++)
{
add(
1,
1,n,max(i-
1,last[j]),j-
1,
1);
dp[j][i]=node[
1].mx+node[
1].sum;
chg(
1,
1,n,j,dp[j][i-
1]);
}
}
cout<<dp[n][k];
}