【题意】:给出一个长度为n的非递减序列,要你将该序列分组,每组至少k个元素,每组的代价是该组每一个元素减去该组的最小值的和,问分组的最小代价。

【题解】:这类型的题目多数是dp实现的。
               设dp[i]表示前 i 个元素分好组的最小代价。
                  dp[i] = min(dp[j] + sum[i] - sum[j] - (i - j) * val[j+1]), k <= j <= i - k;
               考虑到n <= 500000,朴素的dp肯定会超时,优化势在必行。
               这题需要用单调队列优化,我也是从这里学到单调队列优化dp的。
               这里我不详细证明了,很烦人的。
               用单调队列优化之后,复杂度可以降为O(n)。
               笼统一点来说就是,维护凸线,然后用O(1)或者O(logn)的时间来取得最优决策。
               因为这题满足决策的单调性,所以可以把转移降到O(1)。

【代码】:
 1 #include "iostream"
 2 #include "cstdio"
 3 #include "cstring"
 4 #include "algorithm"
 5 #include "vector"
 6 #include "queue"
 7 #include "cmath"
 8 #include "string"
 9 #include "cctype"
10 #include "map"
11 using namespace std;
12 #define pb push_back
13 #define lc(x) (x << 1)
14 #define rc(x) (x << 1 | 1)
15 #define lowbit(x) (x & (-x))
16 #define ll long long
17 #define maxn 500050
18 int n, k;
19 int loc[maxn], head, tail;
20 ll dp[maxn], val[maxn], dq[maxn], sum[maxn];
21 ll cmp[maxn];
22 
23 ll calc2(int a, int b) {
24     return val[a+1] - val[b+1];
25 }
26 
27 int main() {
28     int T;
29     scanf("%d", &T);
30     while(T--) {
31         scanf("%d%d", &n, &k);
32         sum[0] = 0, head = tail = 0, dp[0] = 0;
33         loc[tail++] = 0, cmp[0] = 0;
34         for(int i = 1; i <= n; i++) {
35             scanf("%lld", &val[i]);
36             sum[i] = val[i] + sum[i-1];
37         }
38         for(int i = 1; i <= n; i++) {
39             while(head + 1 < tail && (cmp[loc[head+1]] - cmp[loc[head]]) <= i * calc2(loc[head+1], loc[head])) head++;
40             int j = loc[head];
41             dp[i] = dp[j] + sum[i] - sum[j] - (i - j) * val[j+1];
42             cmp[i] = dp[i] - sum[i] + i * val[i+1];            
43             if(i + 1 >= 2 * k) {
44                 while(head + 1 < tail && (cmp[loc[tail-1]] - cmp[loc[tail-2]]) * calc2(i - k + 1, loc[tail-1]) >= (cmp[i - k + 1] - cmp[loc[tail-1]]) * calc2(loc[tail-1], loc[tail-2])) tail--;
45                 loc[tail++] = i - k + 1;
46             }
47         }
48         printf("%lld\n", dp[n]);
49     }
50     return 0;
51 }
52