您的位置:首页 > 其它

CodeForces 833B The Bakery(dp+线段树优化)

2017-09-26 17:01 429 查看
The Bakery
time limit per test:2.5 seconds
memory limit per test:256 megabytes
input:standard input
output:standard output



Some time ago Slastyona the Sweetmaid decided to open her own bakery! She bought required ingredients and a wonder-oven which can bake several types of cakes, and opened the bakery.

Soon the expenses started to overcome the income, so Slastyona decided to study the sweets market. She learned it's profitable to pack cakes in boxes, and that the more
distinct cake types a box contains (let's denote this number as the
value of the box), the higher price it has.

She needs to change the production technology! The problem is that the oven chooses the cake types on its own and Slastyona can't affect it. However, she knows the types and order of
n cakes the oven is going to bake today. Slastyona has to pack exactly
k boxes with cakes today, and she has to put in each box several (at least one) cakes the oven produced one
right after another (in other words, she has to put in a box a continuous segment of cakes).

Slastyona wants to maximize the total value of all boxes with cakes. Help her determine this maximum possible total value.

Input

The first line contains two integers n and
k (1 ≤ n ≤ 35000,
1 ≤ k ≤ min(n, 50)) – the number of cakes and the number of boxes, respectively.

The second line contains n integers
a1, a2, ..., an
(1 ≤ ai ≤ n) – the types of cakes in the order the oven bakes them.

Output

Print the only integer – the maximum total value of all boxes with cakes.

Examples

Input
4 1
1 2 2 1


Output
2


Input
7 21 3 3 1 4 4 4


Output
5


Input
8 3
7 7 8 7 7 8 1 7


Output
6


        很容易看出转移方程的dp。

        大致题意是,给你n个面包,m个盒子,每个盒子的价值定义为盒子中面包的种类数,然后要求每个盒子都要放至少一个面包,问最后总的价值和最大是多少。很容易写出状态转移方程:dp[i][j]=max(dp[i-1][k-1]+val[k][j]),其中dp[i][j]表示前j个面包用i个盒子装的最大价值总和,val[i][j]表示第i个面包到第j个面包中的面包种类数。现在问题的关键在于时间复杂度,暴力做复杂度是O(NMK)显然需要优化。

        由于这种形式比较像斜率优化,所以我一开始就往斜率优化方面上去想。注意到val[i][j],我可以用线段树维护一段区间,要求的相当于就是一段区间的数字种类数,由于面包种类小于等于35000,所以我可以用线段树,对每个区间维护一个35000位的bitset,每次合并的时候执行或操作,最后统计1出现的次数即可。可以O(logN)的求出val[i][j],在空间上几乎刚好能够满足。但是在进一步推斜率式子的时候便猛然发现,val数组并没有单调性可言,而且无法进行拆分,式子无法表示为斜率的形式,所以以上都是徒劳。更何况此时突然发现线段树每个节点开那么大的bitset也会爆内存……

        所以说得考虑其他方式。注意到,对于一个确定的i,dp[i][j]的值只会从dp[i-1]得来,然后对于决策k的选取,我们是选dp[i-1][k-1]+val[k][j]最大的决策的k,其中i<k<=j。所以说,我们可以考虑对于每一个确定的i,我们用线段树维护dp[i-1][k-1]+val[k][j]的最大值。具体来说,dp[i-1][k-1]这个比较容易操作,至于val[k][j]我们就得考虑动态的处理了。还记得之前多校赛的一道01分数规划问题吗?那题就是求 区间长/区间数字种数 的最值。当时也是利用线段树动态维护种数。对于一个位置i,我们找到他对应数字出现的上一个位置,记为pre[i],那么对于每次新加入进来的数字,我们对区间[pre[i]+1,i]都加上1,表示这个区间内的数字种类数目增加了。如此对应,随着数字被不断加入,线段树中每个点存的就是我们需要的dp[i-1][k-1]+val[k][j],dp[i][j]直接从区间[1,j]的最大值转移过来即可。具体见代码:

#include<bits/stdc++.h>
#define N 40000
using namespace std;

int n,m,ls
,pre
,dp[100]
;

struct ST
{
struct node
{
int max,lazy,l,r;
} tree[N<<2];

inline void push_up(int i)
{
tree[i].max=max(tree[i<<1].max,tree[i<<1|1].max);
}

inline void build(int pos,int i,int l,int r)
{
tree[i].r=r;
tree[i].l=l;
tree[i].lazy=0;
if (l==r)
{
tree[i].max=dp[pos][l-1]; //初始化为dp[i-1]对应的数值
return;
}
int mid=(l+r)>>1;
build(pos,i<<1,l,mid);
build(pos,i<<1|1,mid+1,r);
push_up(i);
}

inline void push_down(int i)
{
tree[i<<1].lazy+=tree[i].lazy;
tree[i<<1|1].lazy+=tree[i].lazy;
tree[i<<1].max+=tree[i].lazy;
tree[i<<1|1].max+=tree[i].lazy;
tree[i].lazy=0;
}

inline void update(int i,int l,int r,int x)
{
if ((tree[i].l==l)&&(tree[i].r==r))
{
tree[i].lazy+=x;
tree[i].max+=x;
return;
}
if (tree[i].lazy!=0) push_down(i);
int mid=(tree[i].l+tree[i].r)>>1;
if (mid>=r) update(i<<1,l,r,x);
else if (mid<l) update(i<<1|1,l,r,x);
else
{
update(i<<1,l,mid,x);
update(i<<1|1,mid+1,r,x);
}
push_up(i);
}

inline int getmax(int i,int l,int r)
{
if ((tree[i].l==l)&&(tree[i].r==r)) return tree[i].max;
if (tree[i].lazy!=0) push_down(i);
int mid=(tree[i].l+tree[i].r)>>1;
if (mid>=r) return getmax(i<<1,l,r);
else if (mid<l) return getmax(i<<1|1,l,r);
else return max(getmax(i<<1,l,mid),getmax(i<<1|1,mid+1,r));
}

} seg;

int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
pre[i]=ls[x]; ls[x]=i; //计算pre数组
}
for(int i=1;i<=m;i++)
{
seg.build(i-1,1,1,n); //每次都重新建树,初始时赋值为dp[i-1]的值
for(int j=i;j<=n;j++)
{
seg.update(1,pre[j]+1,j,1); //动态加入数字
dp[i][j]=seg.getmax(1,1,j); //从区间最大值处转移过来
}
}
printf("%d\n",dp[m]
);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: