CF940E Cashback 線段樹優化DP

liuchanglc發表於2020-07-19

題目描述

Since you are the best Wraith King, Nizhniy Magazin «Mir» at the centre of Vinnytsia is offering you a discount.

You are given an array a a a of length n n n and an integer c c c .

The value of some array b b b of length k k k is the sum of its elements except for the smallest. For example, the value of the array [3,1,6,5,2] [3,1,6,5,2] [3,1,6,5,2] with c=2 c=2 c=2 is 3+6+5=14 3+6+5=14 3+6+5=14 .

Among all possible partitions of a a a into contiguous subarrays output the smallest possible sum of the values of these subarrays.

輸入格式

The first line contains integers n n n and c c c ( 1<=n,c<=100000 1<=n,c<=100000 1<=n,c<=100000 ).

The second line contains n n n integers ai a_{i} ai​ ( 1<=ai<=109 1<=a_{i}<=10^{9} 1<=ai​<=109 ) — elements of a a a .

輸出格式

Output a single integer — the smallest possible sum of values of these subarrays of some partition of a a a .

題意翻譯

給你一個長度為n的數列a和整數c

你需要把它任意分段

每一段假設長度為k,就去掉前\(\lfloor\frac{k}{c}\rfloor\) 小的數

最小化剩下的數的和

輸入輸出樣例

輸入 #1

3 5
1 2 3

輸出 #1

6

輸入 #2

12 10
1 1 10 10 10 10 10 10 9 10 10 10

輸出 #2

92

輸入 #3

7 2
2 3 6 4 5 7 1

輸出 #3

17

輸入 #4

8 4
1 3 4 5 5 3 4 1

輸出 #4

23

說明/提示

In the first example any partition yields 6 as the sum.

In the second example one of the optimal partitions is [1,1],[10,10,10,10,10,10,9,10,10,10] [1,1],[10,10,10,10,10,10,9,10,10,10] [1,1],[10,10,10,10,10,10,9,10,10,10] with the values 2 and 90 respectively.

In the third example one of the optimal partitions is [2,3],[6,4,5,7],[1] [2,3],[6,4,5,7],[1] [2,3],[6,4,5,7],[1] with the values 3, 13 and 1 respectively.

In the fourth example one of the optimal partitions is [1],[3,4,5,5,3,4],[1] [1],[3,4,5,5,3,4],[1] [1],[3,4,5,5,3,4],[1] with the values 1, 21 and 1 respectively.

分析

首先,因為要最小化剩下的數的和,那麼我們肯定要使取走的數的總和儘可能大
我們還可以發現,因為要去掉前\(\lfloor\frac{k}{c}\rfloor\) 小的數
因此只有一段區間的長度大於等於\(c\)時才會對結果產生貢獻
而且我們劃分出長度為\(k\times c + m (1\leq m < c)\)的區間一定不如劃分出長度為\(k\times c\)的區間更優
因為這兩種情況選出的數字的數量相同,但是在第二種情況中選出數的最小值一定不會比前一種情況更小
但是這樣寫還是不太好處理,因為要涉及到前\(k\)小的數,所以似乎要用到某些高階資料結構
而這樣顯然是不好處理的
我們進一步推導會發現,將一個長度為\(k\times c\)的區間劃分為\(k\)個長度為\(c\)的區間所產生的結果只會更優
比如下面一個長度為\(8\)的序列,\(c=4\)
\(1、 1 、2 、5 、3 、7、 8、 9\)
如果我們把它劃分為長度為\(8\)的序列,那麼產生的貢獻為\(1+1=2\)
但是如果我們把它分成兩個長度為\(4\)的序列
\(1、1、2、5\)\(3、7 、8、9\)
那麼產生的貢獻為\(1+3=4\)
顯然後一種更優
因此,我們將原題進一步轉換成將一個長度為\(n\)的序列劃分為若干長度為\(c\)的序列,使每一個序列的最小值之和最大
其中區間最值可以用線段樹去維護
那麼我們可以寫出如下的狀態轉移方程

    for(int i=1;i<=n;i++){
        f[i]=max(f[i],f[i-1]);
        if(i>=c) f[i]=max(f[i],f[i-c]+(long long)jl[i]);
    }

其中\(f[i]\)表示以遍歷到下標為\(i\)的元素所選出的最大價值
\(jl[i]\)表示以\(a[i]\)結尾的長度為\(c\)的區間中的最小值
如果我們選取以\(a[i]\)結尾的長度為\(c\)的區間,那麼\(f[i]=max(f[i],f[i-c]+(long long)jl[i]\)
否則\(f[i]=max(f[i],f[i-1])\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
struct trr{
    int l,r,mmin;
}tr[maxn<<2];
void push_up(int da){
    tr[da].mmin=min(tr[da<<1].mmin,tr[da<<1|1].mmin);
}
void build(int da,int l,int r){
    tr[da].l=l,tr[da].r=r;
    if(l==r){
        tr[da].mmin=a[l];
        return;
    }
    int mids=(l+r)>>1;
    build(da<<1,l,mids);
    build(da<<1|1,mids+1,r);
    push_up(da);
}
int cx(int da,int l,int r){
    if(tr[da].l>=l && tr[da].r<=r){
        return tr[da].mmin;
    }
    int ans=0x3f3f3f3f,mids=(tr[da].l+tr[da].r)>>1;
    if(l<=mids) ans=min(ans,cx(da<<1,l,r));
    if(r>mids) ans=min(ans,cx(da<<1|1,l,r));
    return ans;
}
int jl[maxn];
long long f[maxn];
int main(){
    long long tot=0;
    int n,c;
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        tot+=(long long)a[i];
    }
    build(1,1,n);
    long long ans=0;
    for(int i=1;i<=n-c+1;i++){
        jl[i+c-1]=cx(1,i,i+c-1);
    }
    for(int i=1;i<=n;i++){
        f[i]=max(f[i],f[i-1]);
        if(i>=c) f[i]=max(f[i],f[i-c]+(long long)jl[i]);
    }
    printf("%lld\n",tot-f[n]);
    return 0;
}

相關文章