Codeforces 833B 题解(DP+线段树)

 

题面

传送门:http://codeforces.com/problemset/problem/833/B
B. The Bakery
time limit per test2.5 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard 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
inputCopy
4 1
1 2 2 1
outputCopy
2
inputCopy
7 2
1 3 3 1 4 4 4
outputCopy
5
inputCopy
8 3
7 7 8 7 7 8 1 7
outputCopy
6
Note
In the first example Slastyona has only one box. She has to put all cakes in it, so that there are two types of cakes in the box, so the value is equal to 2.

In the second example it is profitable to put the first two cakes in the first box, and all the rest in the second. There are two distinct types in the first box, and three in the second box then, so the total value is 5.

题目大意:
有一个长度为n的序列,将它分为k段,定义每段的值为该段中不重复元素的个数,求k段值之和的最大值
如:n=4,k=2
1 2 2 1
可分为1,2和2,1两段,值之和为4

分析

1.DP方程的推导

dp[i][j]dp[i][j]表示前j个数分为i段的最大值
状态转移方程为:
dp[i][j]=max(dp[i][j],dp[i1][x1]+num[x][j])dp[i][j]=max(dp[i][j],dp[i−1][x−1]+num[x][j])
其中x[i,j]x∈[i,j]num[x][j]num[x][j]表示区间[x,j][x,j]中不同的数的个数
dp本身的时间复杂度为O(n2k)O(n2k),遍历数组求num的时间复杂度为O(n)O(n),总时间复杂度为O(n3k)O(n3k)

2.求不同数个数的优化

设上一个等于a[i]的数的位置为last[i](,last[i]=0)last[i](如果是第一次,则last[i]=0)
那么
处理到a[j]时,设x从last[j]+1到j,从last[i]+1到j的num[x][j]值均要加1
这是因为从last[i]+1到j的数一定没有a[j],加入a[j]后,不同的数个数一定多了1个
如:
a: 1 2 2 1
last: 0 0 2 1
如果不明白的话,可以在纸上模拟一下过程,但要确保理解上面这句话

3.线段树的优化

我们将状态转移方程中的dp[i1][x1]+num[x][j]dp[i−1][x−1]+num[x][j]看作一个整体,发现可以用线段树维护[i,j]最大值,这样只要一次查询就能求出max
另外,从last[i]+1到i的num[x][i]值均要加1这一操作,可以用区间修改来实现
所以我们只要枚举i从1到k,
每次对上一次的dp值(dp[i-1])建树
再枚举j从i到n,用区间修改,然后查询就能求出dp[i][j]的值
建树复杂度O(nlog2n)O(nlog2n)
n次修改和查询时间复杂度为O(nlog2n)O(nlog2n)
总时间复杂度为O(nklog2n)O(nklog2n)

代码

#include<iostream>
#include<cstdio> 
#include<cstring>
#define maxn 35005
#define maxk 55
using namespace std;
int n,k;
int a[maxn];
int hash[maxn],last[maxn];
int dp[maxk][maxn];     //dp[i][j]表示前j个数分成i段的方法数 
struct node{
    int l;
    int r;
    int v;
    int mark;
}tree[maxn<<2];
void push_up(int pos){
    tree[pos].v=max(tree[pos<<1].v,tree[pos<<1|1].v);
}
void build(int i,int l,int r,int pos){
    tree[pos].l=l;
    tree[pos].r=r;
    tree[pos].mark=0;
    if(l==r){
        tree[pos].v=dp[i][l-1];
        return;
    }
    int mid=(l+r)>>1;
    build(i,l,mid,pos<<1);
    build(i,mid+1,r,pos<<1|1);
    push_up(pos);
}
void push_down(int pos){
    if(tree[pos].mark!=0){
        tree[pos<<1].mark+=tree[pos].mark;
        tree[pos<<1|1].mark+=tree[pos].mark;
        tree[pos<<1].v+=tree[pos].mark;
        tree[pos<<1|1].v+=tree[pos].mark;
        tree[pos].mark=0;
    }
}
void update(int L,int R,int v,int pos){
    if(L<=tree[pos].l&&R>=tree[pos].r){
        tree[pos].v+=v;
        tree[pos].mark+=v;
        return;
    }
    push_down(pos);
    int mid=(tree[pos].l+tree[pos].r)>>1;
    if(L<=mid)update(L,R,v,pos<<1);
    if(R>mid)update(L,R,v,pos<<1|1);
    push_up(pos); 
}
int query(int L,int R,int pos){
    if(L<=tree[pos].l&&R>=tree[pos].r){
        return tree[pos].v;
    }
    push_down(pos);
    int mid=(tree[pos].l+tree[pos].r)>>1;
    int ans=0;
    if(L<=mid)ans=max(ans,query(L,R,pos<<1));
    if(R>mid) ans=max(ans,query(L,R,pos<<1|1));
    return ans;
}
int main(){
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        last[i]=hash[a[i]];//last[i]->a[i]上一次出现的位置 
        hash[a[i]]=i;//hash[x]-> x上一次出现的位置 
    }
    //dp[i][j]=max(dp[i-1][x-1]+num[x][j],dp[i][j]) (i<=x<=j,num[x][j]->[x,j]不同数的个数
    //线段树维护 dp[i-1][x-1]+num[x][j],变成区间查询最大值问题
    //加入a[j],从last[j]+1到j的所有num值都要+1 ,用线段树区间更新 
    for(int i=1;i<=k;i++){
        build(i-1,1,n,1);
        for(int j=i;j<=n;j++){
            update(last[j]+1,j,1,1);
            dp[i][j]=query(i,j,1);
        } 
    }
    printf("%d\n",dp[k][n]);
}
posted @ 2018-05-14 21:40  birchtree  阅读(224)  评论(0编辑  收藏  举报