【題意】:給出一個長度為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