zoukankan      html  css  js  c++  java
  • 单调队列 && 斜率优化dp 专题

    首先得讲一下单调队列,顾名思义,单调队列就是队列中的每个元素具有单调性,如果是单调递增队列,那么每个元素都是单调递增的,反正,亦然。

    那么如何对单调队列进行操作呢?

    是这样的:对于单调队列而言,队首和队尾都可以进行出队操作,但只有队尾能够进行入队操作。

    至于如何来维护单调队列,这里以单调递增队列为例:

    1、如果队列的长度是一定的,首先判断队首元素是否在规定范围内,如果不再,则队首指针向后移动。(至于如何来判断是否在制定范围内,一般而言,我们可以给每个元素设定一个入队的序号,这样就能够知道每个元素原来的顺序了)。

    2、每次加入元素是,如果元素小于队尾元素且队列非空,则减小尾指针,队尾元素出队列,直到保持队列单调性为止。

    题目链接:http://acm.fzu.edu.cn/problem.php?pid=1894

    单调队列的入门题,我们给每个队列中的元素设定一个入队序号,并且设置一个变量来记录当前有多少人离开,这样我们可以维护一个单调递减队列,每次入队的时候,找当前元素适合的位置,每次出队列的时候,判断当前队首元素的入队序号与离开总入数的大小,如果小于等于,则说明当前队首元素应该已经在出队范围内,那么队首指针应该向后移动,直到找到元素的序号比当前离开的总人数大的那个元素,并且出队列。

     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
    ", que[head].val);
    48             } else 
    49                 level++;
    50         }
    51     }
    52     return 0;
    53 }
    View Code

    题目链接:http://poj.org/problem?id=2823

    比较裸的单调队列,可以开两个队列来保存结果,一个单调递增来保存最小值,一个单调递减来保存最大值,每个元素入队列时都给一个入队编号,然后我们在判断的时候,只要判断当前元素的序号与队首元素的序号相差不大与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
    ", ans1[i]);
    73             else printf("%d ", ans1[i]);
    74         }
    75         for (int i = 1; i <= M; i++) {
    76             if (i == M) printf("%d
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", dp[M + 1][N]);
    67     }
    68     return 0;
    69 }
    70 
    71 
    72                 
    View Code

    PS:单调队列做多了,就能发现只要推出递推方程,然后转化为斜率,那么剩下的基本上就是模板题了!

  • 相关阅读:
    POJ 2018 二分
    873. Length of Longest Fibonacci Subsequence
    847. Shortest Path Visiting All Nodes
    838. Push Dominoes
    813. Largest Sum of Averages
    801. Minimum Swaps To Make Sequences Increasing
    790. Domino and Tromino Tiling
    764. Largest Plus Sign
    Weekly Contest 128
    746. Min Cost Climbing Stairs
  • 原文地址:https://www.cnblogs.com/wally/p/3593224.html
Copyright © 2011-2022 走看看