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

  • 相关阅读:
    微信公众号迁移配置注意点
    关于memcache 命令查看和 Telnet
    centOS 安装(光安装 和 u盘安装)
    CentOS删除自带的java,安装新java
    ubuntu常用命令
    ubuntu 的挂起与休眠
    saiku应用的调试
    数据挖掘123
    unbutu 安装java教程
    workbench的schema讲解一:(维度dimension设置的基本内容)
  • 原文地址:https://www.cnblogs.com/wally/p/3593224.html
Copyright © 2011-2022 走看看