1. 程式人生 > >CF940E Cashback 線段樹優化DP

CF940E Cashback 線段樹優化DP

## 題目描述 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$的序列,使每一個序列的最小值之和最大 其中區間最值可以用線段樹去維護 那麼我們可以寫出如下的狀態轉移方程 ``` cpp 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])$ ## 程式碼 ``` cpp #include
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