题意可以总结为把一堆数分割成k份,每份的权值定义为里面不同数个数,要让总的权值最大。
这种类型的题目第一反应要想到用dp,有两个参数,第一维是k份,第二维是物品个数。dp[i][j]。
然后分析转移。
对于某个物品,他要产生贡献,则必定是到上一次出现这个4号之前的位置都能加一。而这个位置而言指的是如果在这个位置断开,新开一个区间来装4这个物品就能总的权值加1,比如说在这个物品的前一个4还要前的位置断开,这个区间就有两个4了,并不能让总的权值加1。
理解完状态的转移之后,可以用n的平方方法来往前找dp[i][j]=MAX(dp[i-1][0~prej-1],dp[i-1][prej~j-1]+1)。这就像一个前一个dp中来一个区间更新,然后再找查询0~j-1中的dp最大值进行转移。也就是很像线段树的操作,所以可以用线段树进行维护这个dp转移。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 35000+10;
int n,k;
int cake[MAXN];
class Seg//线段树查询区间最大值
{
int maxv[MAXN*4];
int add[MAXN*4];
void pushdown(int rt)
{
if(add[rt])
{
add[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
maxv[rt<<1]+=add[rt];
maxv[rt<<1|1]+=add[rt];
add[rt]=0;
}
}
void pushup(int rt)
{
maxv[rt]=max(maxv[rt<<1],maxv[rt<<1|1]);
}
public:
void build(int rt,int l,int r)
{
add[rt]=0;
if(l==r)
{
maxv[rt]=add[rt]=0;
pushup(rt);
return;
}
int m=l+r>>1;
build(rt<<1,l,m);
build(rt<<1|1,m+1,r);
pushup(rt);
}
void updata(int rt,int l,int r,int left,int right,int value)
{
if(l>=left&&r<=right)
{
maxv[rt]+=value;
add[rt]+=value;
return;
}
pushdown(rt);
int m=l+r>>1;
if(left<=m) updata(rt<<1,l,m,left,right,value);
if(right>=m+1) updata(rt<<1|1,m+1,r,left,right,value);
pushup(rt);
}
int query(int rt,int l,int r,int left,int right)
{
if(left<=l&&right>=r)
return maxv[rt];
if(left>r||right<l)
return 0;
pushdown(rt);
int m=l+r>>1;
return max(query(rt<<1,l,m,left,right),query(rt<<1|1,m+1,r,left,right));
}
}seg[55];
int last[MAXN];
int pos[MAXN];
int main()
{
if (fopen("in.txt", "r") != NULL)
{
freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
}
cin>>n>>k;
for(int i=1;i<=n;i++)
{
scanf("%d",cake+i);
last[i]=pos[cake[i]];//这个颜色上一次出现的位置
pos[cake[i]]=i;//更新位置
}
seg[0].build(1,0,n);
for(int i=1;i<=k;i++)
{
seg[i].build(1,0,n);
for(int j=1;j<=n;j++)
{
seg[i-1].updata(1,0,n,last[j],j-1,1);
int dp=seg[i-1].query(1,0,n,0,j-1);
seg[i].updata(1,0,n,j,j,dp);
}
}
cout<<seg[k].query(1,0,n,0,n);
return 0;
}