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] 表示前j個數分爲i段的最大值
狀態轉移方程爲:
dp[i][j]=max(dp[i][j],dp[i1][x1]+num[x][j])
其中x[i,j]num[x][j] 表示區間[x,j] 中不同的數的個數
dp本身的時間複雜度爲O(n2k) ,遍歷數組求num的時間複雜度爲O(n) ,總時間複雜度爲O(n3k)

2.求不同數個數的優化

設上一個等於a[i]的數的位置爲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] 看作一個整體,發現可以用線段樹維護[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)
n次修改和查詢時間複雜度爲O(nlog2n)
總時間複雜度爲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]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章