單調隊列 && 斜率優化dp 專題

首先得講一下單調隊列,顧名思義,單調隊列就是隊列中的每一個元素具備單調性,若是是單調遞增隊列,那麼每一個元素都是單調遞增的,反正,亦然。php

那麼如何對單調隊列進行操做呢?html

是這樣的:對於單調隊列而言,隊首和隊尾均可以進行出隊操做,但只有隊尾可以進行入隊操做。ios

至於如何來維護單調隊列,這裏以單調遞增隊列爲例:算法

一、若是隊列的長度是必定的,首先判斷隊首元素是否在規定範圍內,若是再也不,則隊首指針向後移動。(至於如何來判斷是否在制定範圍內,通常而言,咱們能夠給每一個元素設定一個入隊的序號,這樣就可以知道每一個元素原來的順序了)。數組

二、每次加入元素是,若是元素小於隊尾元素且隊列非空,則減少尾指針,隊尾元素出隊列,直到保持隊列單調性爲止。ide

 

題目連接:http://acm.fzu.edu.cn/problem.php?pid=1894優化

單調隊列的入門題,咱們給每一個隊列中的元素設定一個入隊序號,而且設置一個變量來記錄當前有多少人離開,這樣咱們能夠維護一個單調遞減隊列,每次入隊的時候,找當前元素適合的位置,每次出隊列的時候,判斷當前隊首元素的入隊序號與離開總入數的大小,若是小於等於,則說明當前隊首元素應該已經在出隊範圍內,那麼隊首指針應該向後移動,直到找到元素的序號比當前離開的總人數大的那個元素,而且出隊列。spa

 1 /*************************************************************************
 2     > File Name: fzu1894.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月11日 星期二 08時55分28秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 using namespace std;
11 
12 const int MAXN = (1000000 + 10);
13 struct Node {
14     int val, num;
15 };
16 
17 Node que[MAXN];
18 
19 int main()
20 {
21     char s1[7], s2[7];
22     int Case;
23     scanf("%d", &Case);
24     while (Case--) {
25         int head = 0, tail = -1, val;
26         int num = 0, level = 0;
27         scanf("%s", s1);
28         while (~scanf("%s", s1)) {
29             if (strcmp(s1, "END") == 0) {
30                 break;
31             }
32             if (s1[0] == 'C') {
33                 scanf("%s %d", s2, &val);
34                 //找到當前值適合插入的位置,而且將其後面的元素所有捨棄
35                 while (head <= tail && que[tail].val <= val) tail--;
36                 que[++tail].val = val;
37                 que[tail].num = ++num;
38             } else if (s1[0] == 'Q') {
39                 //level記錄了有多少個離開,所以咱們要找的是隊頭元素進隊列時的序號大於
40                 //目前離開的總人數,這樣纔可以說明當前元素還在隊列中
41                 while (head <= tail && que[head].num <= level) {
42                     head++;
43                 }
44                 if (tail < head) {
45                     puts("-1");
46                 } else 
47                     printf("%d\n", que[head].val);
48             } else 
49                 level++;
50         }
51     }
52     return 0;
53 }
View Code

 

題目連接:http://poj.org/problem?id=28233d

比較裸的單調隊列,能夠開兩個隊列來保存結果,一個單調遞增來保存最小值,一個單調遞減來保存最大值,每一個元素入隊列時都給一個入隊編號,而後咱們在判斷的時候,只要判斷當前元素的序號與隊首元素的序號相差不大與K,則最值就是當前隊首元素,不然,隊首指針向後移動,直到找到一個符合條件的元素。指針

 1 /*************************************************************************
 2     > File Name: poj2823.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月11日 星期二 09時45分04秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 #include <vector>
11 using namespace std;
12 
13 const int MAXN = (1000000 + 100);
14 struct Node {
15     int val, index;
16 };
17 
18 Node que1[MAXN], que2[MAXN];
19 int N, K, M;
20 int num[MAXN];
21 int ans1[MAXN], ans2[MAXN];
22 
23 void getSolve1()
24 {
25     int head = 0, tail = -1, len = K;
26     M = 0;
27     for (int i = 1; i <= N; i++) {
28         while (head <= tail && num[i] <= que1[tail].val) {
29             tail--;
30         }
31         que1[++tail].val = num[i];
32         que1[tail].index = i;
33         if (i - len == 0) {
34             while (head <= tail && i - que1[head].index + 1 > K) {
35                 head++;
36             }
37             ans1[++M] = que1[head].val;
38             len++;
39         }
40     }
41 }
42 
43 void getSolve2()
44 {
45     int head = 0, tail = -1, len = K;
46     M = 0;
47     for (int i = 1; i <= N; i++) {
48         while (head <= tail && num[i] >= que2[tail].val) {
49             tail--;
50         }
51         que2[++tail].val = num[i];
52         que2[tail].index = i;
53         if (i - len == 0) {
54             while (head <= tail && i - que2[head].index + 1 > K) {
55                 head++;
56             }
57             ans2[++M] = que2[head].val;
58             len++;
59         }
60     }
61 }
62 
63 int main()
64 {
65     while (~scanf("%d %d", &N, &K)) {
66         for (int i = 1; i <= N; i++) {
67             scanf("%d", &num[i]);
68         }
69         getSolve1();
70         getSolve2();
71         for (int i = 1; i <= M; i++) {
72             if (i == M) printf("%d\n", ans1[i]);
73             else printf("%d ", ans1[i]);
74         }
75         for (int i = 1; i <= M; i++) {
76             if (i == M) printf("%d\n", ans2[i]);
77             else printf("%d ", ans2[i]);
78         }
79     }
80     return 0;
81 }
View Code

 

題目連接:http://acm.hdu.edu.cn/showproblem.php?pid=3415

題目的意思就是讓你求最大的長度不超過K的連續序列的和。

思路:因爲序列的環狀特色,能夠在最後添加K-1個數,而且用sum[i]表示1到i的連續和,因而sum[j] - sum[i - 1]就是i到j的連續和了。

那麼對於每個sum[j],用sum[j]來減去最小的sum[i](知足j - i >= K - 1),這樣的話,就能夠用單調隊列來維護最小sum[i]下標了。

 1 /*************************************************************************
 2     > File Name: hdu3415.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月11日 星期二 10時43分42秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <climits>
10 #include <algorithm>
11 #include <deque>
12 using namespace std;
13 
14 const int MAXN = (200000 + 200);
15 int N, K;
16 int sum[MAXN], num[MAXN];
17 int que[MAXN];
18 
19 int main()
20 {
21     int Case;
22     scanf("%d", &Case);
23     while (Case--) {
24         scanf("%d %d", &N, &K);
25         sum[0] = 0;
26         for (int i = 1; i <= N; i++) {
27             scanf("%d", &num[i]);
28             sum[i] = sum[i - 1] + num[i];
29         }
30         for (int i = N + 1; i <= N + K - 1; i++) {
31             sum[i] = sum[i - 1] + num[i - N];
32         }
33         int head = 0, tail = -1;
34         deque<int > deq;
35         int st, ed, ans = INT_MIN; 
36         for (int i = 1; i <= N + K - 1; i++) {
37             while (head <= tail && sum[i - 1] < sum[que[tail]]) {
38                 tail--;
39             }
40             while (head <= tail && i - que[head] > K) {
41                 head++;
42             }
43             que[++tail] = i - 1;
44             if (sum[i] - sum[que[head]] > ans) {
45                 ans = sum[i] - sum[que[head]];
46                 st = que[head] + 1;
47                 ed = i;
48             }
49             /*
50             while (!deq.empty() && sum[i - 1] < sum[deq.back()]) {
51                 deq.pop_back();
52             }
53             while (!deq.empty() && i - deq.front() > K) {
54                 deq.pop_front();
55             }
56             deq.push_back(i - 1);
57             if (sum[i] - sum[deq.front()] > ans) {
58                 ans = sum[i] - sum[deq.front()];
59                 st = deq.front() + 1;
60                 ed = i;
61             }*/
62         }
63         if (ed > N) ed -= N;
64         printf("%d %d %d\n", ans, st, ed);
65     }
66     return 0;
67 }
View Code

 

題目連接:http://acm.hdu.edu.cn/showproblem.php?pid=3507

這是個人第一道斜率優化的題目,整整看了一個下午和一個晚上的時間纔有點明白過來。

下面的這位大牛寫的很好:http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html

我本身的代碼中也已有詳細的註釋,純粹是對這題的理解!

 1 /*************************************************************************
 2     > File Name: hdu3507.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月11日 星期二 21時13分52秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 #include <cmath>
11 using namespace std;
12 
13 const int MAXN = (500000 + 500);
14 int N, M;
15 int num[MAXN], sum[MAXN];
16 int dp[MAXN];
17 int que[MAXN];
18 
19 int getUp(int j, int k)
20 {
21     return (dp[j] + sum[j] * sum[j]) - (dp[k] + sum[k] * sum[k]);
22 }
23 
24 int getDown(int j, int k)
25 {
26     return 2 * sum[j] - 2 * sum[k];
27 }
28 
29 int getDp(int i, int j)
30 {
31     return dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + M;
32 }
33 
34 
35 int main()
36 {
37     while (~scanf("%d %d", &N, &M)) {
38         sum[0] = 0;
39         for (int i = 1; i <= N; i++) {
40             scanf("%d", &num[i]);
41             sum[i] = sum[i - 1] + num[i];
42         }
43         int head = 0, tail = 0;
44         for (int i = 1; i <= N; i++) {
45             //這裏我假設,當k < j < i時,若是j比k優的話,有:
46             //dp[j] + (sum[i] - sum[j]) ^ 2 + M <= dp[k] + (sum[i] - sum[k]) ^ 2 + M;
47             //化簡即有:(dp[j]+ sum[j] ^ 2) - (d[k] + sum[k] ^ 2) <= sum[i] * 2(sum[j] - sum[k])
48             //令yj = dp[j] + sum[j] ^ 2, yk = dp[k] + sum[k] ^ 2;
49             //xj = 2 * sum[j], xk = 2 * sum[k];
50             //因而有(yj - yk)/(xj - xk) <= sum[i]; 這裏簡記爲g[j, k] <= sum[i];
51             //因爲我這裏假設k < j < i時,j比k優,說明若是知足上面的不等式,k是取不到的
52             //因而就能夠把k(歸納的講是j前面的數字剔除掉,因而有了下面head指針的移動
53             while (head < tail && getUp(que[head + 1], que[head])
54                     <= sum[i] * getDown(que[head + 1], que[head])) {
55                 head++;
56             }
57             //根據等式dp[i] = dp[j] + (sum[i] - sum[j]) ^ 2 + M;
58             //此時que[head]保留的就是最優值
59             //這樣每次求得的dp[i]就都是最有的了
60             dp[i] = getDp(i, que[head]);
61             //上面假設k < j < i,當我加入新元素x時,有k < j < i < x,如有g[x, i] <= g[i, j];
62             //那麼說明此時新加入的x點比原來的i點更優,因而應該替換原來的點i,因而就有了下面
63             //的tail指針左移的狀況
64             while (head < tail && getUp(i, que[tail]) * getDown(que[tail], que[tail - 1]) 
65                     <= getUp(que[tail], que[tail - 1]) * getDown(i, que[tail])) {
66                 tail--;
67             }
68             que[++tail] = i;
69         }
70         printf("%d\n", dp[N]);
71     }
72     return 0;
73 }
74 
75 
76  
View Code

 

題目連接:http://acm.hdu.edu.cn/showproblem.php?pid=3480

思路:狀態方程很容易寫,dp[i][j]表示前i個數,分紅j組的最小值,因而能夠得出方程:dp[i][j] = min(dp[k][j - 1] + (num[i] - num[k + 1) ^ 2) (其中1 <= k < i).但是這個方程的複雜度但是O(n * m * n)。。對於n <=10000, m <=5000這樣的數據規模顯然是吃不消的。。。

怎麼辦呢?

能夠試試斜率優化:這裏咱們假設對於k1 < k2 < i.方程在k2處的取值因爲在k1處的取值,因而有

dp[k2][j-1] + (num[i]- num[k2 + 1]) ^2 <=  dp[k1][j-1] + (num[i]- num[k1 + 1]) ^ 2;

兩邊移項化簡可得:dp[k2][j-1] + num[k2+ 1] ^2 - (dp[k1][j-1] + num[k1+ 1] ^2) <= num[i] * (2 * num[k2 + 1] - 2 * num[k1 + 1]);

咱們令

yk2 = dp[k2][j-1] + num[k2 + 1] ^ 2;

yk1 = dp[k1][j- 1] + num[k1 + 1] ^ 2;

xk2 = 2 * num[k2 + 1];

xk1 = 2 * num[k1 + 1];

因而有:(yk2 - yk1)/(xk2 - xk1) <= num[i].

這裏咱們簡記爲g[k2, k1] = (yk2 - yk1)/(xk2 - xk1);

因爲咱們一開始假設對於k1 < k2 < i,有k2比k1優,此時知足的條件是g[k2, k1] <= num[i],那麼放過來講,當咱們的方程知足g[k2, k1] <= num[i]時,k2比k1優,此時就能夠去掉k1,也就是單調隊列中的頭指針向後移動。

假設對於k1 < k2 < k3,有g[k3, k2] <= g[k2, k1].因爲咱們以前假設當k2優於k1時有g[k2, k1] <= num[i],則g[k3, k2] <= g[k2,k1] <= num[i].因而就有k3 優於k2,又由於k2優於k1,說明k2是永遠都取不到的,這樣的話,咱們能夠直接把k2從隊尾刪除。而後咱們重複上一步驟,直到g[k3, k2] > g[k2, k1].

 1 /*************************************************************************
 2     > File Name: hdu3480.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月12日 星期三 09時51分55秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 using namespace std;
11 
12 const int MAXN = (10000 + 100);
13 const int MAXM = (5000 + 50);
14 int N, M;
15 int num[MAXN];
16 int dp[MAXN][MAXM];
17 int que[MAXN];
18 
19 //k1 < k2
20 //yk2 - yk1部分
21 int getUp(int k1, int k2, int j)
22 {
23     int yk2 = dp[k2][j - 1] + num[k2 + 1] * num[k2 + 1];
24     int yk1 = dp[k1][j - 1] + num[k1 + 1] * num[k1 + 1];
25     return yk2 - yk1;
26 }
27 
28 //k1 < k2
29 //xk2 - xk1部分
30 int getDown(int k1, int k2)
31 {
32     int xk2 = 2 * num[k2 + 1];
33     int xk1 = 2 * num[k1 + 1];
34     return xk2 - xk1;
35 }
36 
37 //dp[i][j] = dp[k][j - 1] + (num[i] - num[k + 1]) ^ 2;
38 int getDp(int i, int k, int j)
39 {
40     return dp[k][j - 1] + (num[i] - num[k + 1]) * (num[i] - num[k + 1]);
41 }
42 
43 
44 int main()
45 {
46     int Case, t = 1;
47     scanf("%d", &Case);
48     while (Case--) {
49         scanf("%d %d", &N, &M);
50         for (int i = 1; i <= N; i++) {
51             scanf("%d", &num[i]);
52         }
53         sort(num + 1, num + 1 + N);
54         for (int i = 1; i <= N; i++) {
55             dp[i][1] = (num[i] - num[1]) * (num[i] - num[1]);
56         }
57         que[0] = 1;
58         for (int j = 2; j <= M; j++) {
59             int head = 0, tail = 0;
60             for (int i = j; i <= N; i++) {
61                 while (head < tail && getUp(que[tail], i, j) * getDown(que[tail - 1], que[tail]) <= getUp(que[tail - 1], que[tail], j) * getDown(que[tail], i)) {
62                     tail--;
63                 }
64                 que[++tail] = i;
65                 while (head < tail && getUp(que[head], que[head + 1], j)
66                         <= num[i] * getDown(que[head], que[head + 1])) {
67                     head++;
68                 }
69                 dp[i][j] = getDp(i, que[head], j);
70             }
71         }
72         printf("Case %d: %d\n", t++, dp[N][M]);
73     }
74     return 0;
75 }
76 
77 
78 
79                 
View Code

 

題目連接:http://poj.org/problem?id=3709

思路:狀態方程很容易想,dp[i]表示處理到i爲止的最小值,因而有dp[i] = min(dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);

對於 n <= 500000的數據規模,O(n^2)的算法必然要T。

這裏能夠用斜率優化.

假設對於k1 < k2 < i有k2處的值優於k1處的值,因而有dp[k2] + (sum[i] - sum[k2] - (i - k2) * (num[k2 + 1]) <= dp[k1] + (sum[i] - sum[k1] - (i - k1)* (num[k1 + 1]));

化簡後可得:(dp[k2] - sum[k2] + k2 * num[k2 + 1]) - (dp[k1] - sum[k1] + k1 * num[k1 + 1]) <= i * (num[k2 + 1] - num[k1 + 1]);

令yk2 = dp[k2] - sum[k2] + k2 * num[k2 + 1];

yk1 = dp[k1] - sum[k1] + k1 * num[k1 + 1];

xk2 = num[k2 + 1];

xk1 = num[k1 + 1];

因而有(yk2 - yk1) <= i * (xk2 - xk1);

因爲咱們一開始假設k1 < k2 < i,有k2優於k1,也就是說若是知足上述方程:g[k2, k1] = (yk2 - yk1)/ (xk2 - xk1) <= i成立,那麼k2就比k1優,也就是說k1是取不到的,由此隊首指針要向後移動。

若k1 < k2 < k3 ,若是有g[k3, k2 ] <= g[k2, k1] 因爲g[k2, k1] <= i, 那麼g[k3, k2] <= i,也就是說k3比k2優,又k2比k1優,因而k2是取不到的,那麼k2能夠從隊尾刪除。

 1 /*************************************************************************
 2     > File Name: poj3709.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月12日 星期三 16時32分07秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 using namespace std;
11 
12 const int MAXN = (500000 + 50);
13 typedef long long ll;
14 int N, K;
15 ll num[MAXN], sum[MAXN];
16 ll dp[MAXN];
17 ll que[MAXN];
18 
19 //yk2 - yk1 , k1 < k2 < i
20 ll getUp(int k1, int k2)
21 {
22     ll yk1 = dp[k1] - sum[k1] + k1 * num[k1 + 1];
23     ll yk2 = dp[k2] - sum[k2] + k2 * num[k2 + 1];
24     return yk2 - yk1;
25 }
26 
27 //xk2 - xk1 
28 ll getDown(int k1, int k2)
29 {
30     return num[k2 + 1] - num[k1 + 1];
31 }
32 
33 //dp[i] = dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);
34 ll getDp(int i, int j)
35 {
36     return dp[j] + (sum[i] - sum[j] - (i - j) * num[j + 1]);
37 }
38 
39 
40 int main()
41 {
42     int Case;
43     scanf("%d", &Case);
44     while (Case--) {
45         scanf("%d %d", &N, &K);
46         sum[0] = 0;
47         for (int i = 1; i <= N; i++) {
48             scanf("%lld", &num[i]);
49             sum[i] = sum[i - 1] + num[i];
50         }
51         int head = 0, tail = 0;
52         for (int i = 1; i <= N; i++) {
53             while (head < tail && getUp(que[head], que[head + 1])
54                     <= i * getDown(que[head], que[head + 1])) {
55                 head++;
56             }
57             dp[i] = getDp(i, que[head]);
58             //因爲咱們要加入的數是i - (k - 1),可是要保證前一組的數至少有k個相同
59             if (i - (K - 1) >= K) {
60                 int x = i - (K - 1);
61                 while (head < tail && getUp(que[tail], x) * getDown(que[tail - 1], que[tail]) <= getUp(que[tail - 1], que[tail]) * getDown(que[tail], x)) {
62                     tail--;
63                 }
64                 que[++tail] = x;
65             }
66         }
67         printf("%lld\n", dp[N]);
68     }
69     return 0;
70 }
View Code

 

題目連接:http://poj.org/problem?id=1180

狀態方程比較難想。

dp[i] 表示第i個任務到n的最小花費,因而有dp[i] = min{dp[j] + (S + sumT[i] - sumT[j]) * (sumF[i] - sumF[j]) + (S + sumT[i] - sumT[j]) * sumF[j]} ;

化簡後即得:dp[i] = min{dp[j] + (S + sumT[i] - sumT[j]) * sumF[i];

因而令k1 < k2 有k1 優於k2....步驟基本上就是同樣的了。

 1 /*************************************************************************
 2     > File Name: poj1180.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月12日 星期三 21時26分48秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 using namespace std;
11 
12 const int MAXN = (10000 + 100);
13 int N, S;
14 int t[MAXN], f[MAXN];
15 int sumT[MAXN], sumF[MAXN];
16 int dp[MAXN];
17 int que[MAXN];
18 
19 //yk2 - yk1, k1 < k2;
20 int getUp(int k1, int k2)
21 {
22     return dp[k1] - dp[k2];
23 }
24 
25 //xk2 - xk1;
26 int getDown(int k1, int k2)
27 {
28     return sumT[k1] - sumT[k2];
29 }
30 
31 int getDp(int i, int j)
32 {
33     return dp[j] + (S + sumT[i] - sumT[j]) * sumF[i];
34 }
35 
36 
37 int main()
38 {
39     while (~scanf("%d %d", &N, &S)) {
40         dp[N + 1] = sumT[N + 1] = sumF[N + 1] = 0;
41         for (int i = 1; i <= N; i++) {
42             scanf("%d %d", &t[i], &f[i]);
43         }
44         for (int i = N; i >= 1; i--) {
45             sumT[i] = sumT[i + 1] + t[i];
46             sumF[i] = sumF[i + 1] + f[i];
47         }
48         int head = 0, tail = -1;
49         que[++tail] = N + 1;
50         for (int i = N; i >= 1; i--) {
51             while (head < tail && getUp(que[head + 1], que[head])
52                     <= sumF[i] * getDown(que[head + 1], que[head])) {
53                 head++;
54             }
55             dp[i] = getDp(i, que[head]);
56             while (head < tail && getUp(i, que[tail]) * getDown(que[tail], que[tail - 1])
57                     <= getUp(que[tail], que[tail - 1]) * getDown(i, que[tail])) {
58                 tail--;
59             }
60             que[++tail] = i;
61         }
62         printf("%d\n", dp[1]);
63     }
64     return 0;
65 }
View Code

 

題目連接:http://acm.hdu.edu.cn/showproblem.php?pid=2993

思路:ans[i] = min{(sum[i] - sum[j]) / (i - j)), 咱們把(i, sum[i])當作一個點,那麼不就是求斜率的最大值嗎?因爲數據規模爲10萬級別,O(N^2)的算法必然要T。

因而能夠用單調隊列來優化!

 1 /*************************************************************************
 2     > File Name: hdu2993.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月12日 星期三 22時26分13秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 #include <cmath>
11 using namespace std;
12 
13 const int MAXN = (100000 + 100);
14 template < class T > inline T getMax(const T &a, const T &b)
15 {
16     return a > b ? a : b;
17 }
18 
19 int N, K, num[MAXN];
20 double sum[MAXN];
21 double ans;
22 int que[MAXN];
23 
24 int main()
25 {
26     while (~scanf("%d %d", &N, &K)) {
27         sum[0] = 0;
28         for (int i = 1; i <= N; i++) {
29             scanf("%d", &num[i]);
30             sum[i] = sum[i - 1] + num[i] * 1.0;
31         }
32         int head = 0, tail = -1;
33         que[++tail] = 0;
34         ans = 0.0;
35         for (int i = K; i <= N; i++) {
36             int index =  i - K;
37             while (head < tail) {
38                 double y1 = sum[que[tail]] - sum[que[tail - 1]];
39                 double x1 = que[tail] - que[tail - 1];
40                 double y2 = sum[index] - sum[que[tail]];
41                 double x2 = index - que[tail];
42                 if (y1 * x2 >=  y2 * x1) tail--;
43                 else break;
44             }
45             que[++tail] = index;
46             while (head < tail) {
47                 double y1 = sum[que[head]] - sum[i];
48                 double x1 = que[head] - i;
49                 double y2 = sum[que[head + 1]] - sum[i];
50                 double x2 = que[head + 1] - i;
51                 if (y1 * x2 <= y2 * x1) head++;
52                 else break;
53             }
54             ans = getMax(ans, (sum[i] - sum[que[head]]) / (i - que[head]) * 1.0);
55         }
56         printf("%.2lf\n", ans);
57     }
58     return 0;
59 }
View Code

 

題目連接:http://acm.hdu.edu.cn/showproblem.php?pid=2829

思路:dp[i][j]表示前j個數組成i組的最小值,w[i]表示1-i的價值,sum[i]表示1-i的和。因而咱們能夠得出遞推方程:dp[i][j] = min{dp[i-1][k] + w[j] - w[k] - sum[k] *  (sum[j] - sum[k])} (i<= k < j);

毫無疑問,若是按照通常的解法,那麼複雜度將是O(n^3),對於n<= 1000的規模顯吃不消,那怎麼辦呢,試試斜率優化!

咱們設k1 < k2 < i時,k2優於k1,因而能夠獲得:dp[i-1][k2] + w[j] - w[k2] - sum[k2] * (sum[j] - sum[k2]) <= dp[i-1][k1] + w[j] - w[k1] - sum[k1] * (sum[j] - sum[k1]);

化簡後可得:dp[i-1][k2] - w[k2] + sum[k2] * sum[k2] - (dp[i-1][k1] - w[k1] + sum[k1] * sum[k1]) <= sum[j] * (sum[k2- sum[k1]) ,令

yk1 = dp[i-1][k1] - w[k1] + sum[k1] * sum[k1];

yk2 = dp[i-1][k2] - w[k2] + sum[k2] * sum[k2];

xk1 = sum[k1];

xk2 = sum[k2];

因而有(yk2- yk1)<= sum[j] * (xk2- xk1).因爲咱們一開始是假設當k1 < k2 < i時,k2處的取值優於k1,因而咱們能夠得出當知足(yk2 - yk1) <= sum[i] * (xk2 -xk1)(這裏我爲了方便起見,簡記爲g[k2, k1] = (yk2 - yk1)/ (xk2 - xk1))時,k2比k1優,那麼也就是說k1是取不到的,因而這是咱們應該移動隊首指針,將k1從隊首刪除。

設k1 < k2 < k3< i,若是咱們有g[k3, k2] <= g[k2, k1],因爲g[k2, k1] <= sum[j],因而g[k3,k2] <= sum[j],那麼也就是說k3處因爲k2處,又k2優於k1,說明k2是取不到的,因而k2也能夠從隊尾刪除。

這裏說一下單調隊列的做用,從遞推關係式能夠看出,咱們是要找當前最優的K值,那麼que這個單調隊列的隊首保存的就是當前最有的K值。

 1 /*************************************************************************
 2     > File Name: hdu2829.cpp
 3     > Author: syhjh
 4     > Created Time: 2014年03月13日 星期四 19時50分07秒
 5  ************************************************************************/
 6 #include <iostream>
 7 #include <cstdio>
 8 #include <cstring>
 9 #include <algorithm>
10 using namespace std;
11 
12 const int MAXN = (1000 + 10);
13 int N, M;
14 int num[MAXN], sum[MAXN], w[MAXN]; //w[i]表示1-i算一組的val
15 int dp[MAXN][MAXN]; //dp[i][j]表示前j個數分紅i組的最小val
16 int que[MAXN];
17 
18 //yk2 - yk1, k1 < k2;
19 int getUp(int k1, int k2, int i)
20 {
21     int yk1 = dp[i - 1][k1] - w[k1] + sum[k1] * sum[k1];
22     int yk2 = dp[i - 1][k2] - w[k2] + sum[k2] * sum[k2];
23     return yk2 - yk1;
24 }
25 
26 //xk2 - xk1
27 int getDown(int k1, int k2)
28 {
29     return sum[k2] - sum[k1];
30 }
31 
32 //dp[i][j] = dp[i - 1][k] + (w[j] - w[k] - sum[k] * (sum[j] - sum[k]));
33 int getDp(int i, int j, int k)
34 {
35     return dp[i - 1][k] + (w[j] - w[k] - sum[k] * (sum[j] - sum[k]));
36 }
37 
38 int main()
39 {
40     while (~scanf("%d %d", &N, &M)) {
41         if (N == 0 && M == 0) break;
42         sum[0] = w[0] = 0;
43         for (int i = 1; i <= N; i++) {
44             scanf("%d", &num[i]);
45             sum[i] = sum[i - 1] + num[i];
46             w[i] = w[i - 1] + sum[i - 1] * num[i];
47         }
48         for (int i = 1; i <= N; i++) {
49             dp[1][i] = w[i];
50         }
51         for (int i = 2; i <= M + 1; i++) {
52             int head = 0, tail = -1;
53             que[++tail] = i - 1;
54             for (int j = i; j <= N; j++) {
55                 while (head < tail && getUp(que[head], que[head + 1], i)
56                          <= sum[j] * getDown(que[head], que[head + 1])) {
57                     head++;
58                 }
59                 dp[i][j] = getDp(i, j, que[head]);
60                 while (head < tail && getUp(que[tail], j, i) * getDown(que[tail - 1], que[tail]) <= getUp(que[tail - 1], que[tail], i) * getDown(que[tail], j)) {
61                     tail--;
62                 }
63                 que[++tail] = j;
64             }
65         }
66         printf("%d\n", dp[M + 1][N]);
67     }
68     return 0;
69 }
70 
71 
72                 
View Code

PS:單調隊列作多了,就能發現只要推出遞推方程,而後轉化爲斜率,那麼剩下的基本上就是模板題了!

相關文章
相關標籤/搜索