单调队列优化DP动态规划(1)

一.前言

学好了单调队列不仅可以单独使用,他还可以有更多的广泛用途。这里我主要讲其对动态规划的优化,较简单。


二.例题1:单调队列本身的灵活应用——测量温度

在讲用单调队列优化DP前要先讲一讲单调队列本身的灵活应用,所以引入一道题目——测量温度。


1.题目

题目描述

某国进行了连续N(1<=N<=1000000天的温度测量,测量存在误差,测量结果是第i天温度在[l_i,r_i]范围内。其中-10^9<l_i<=r_i<=10^9
求最长的连续的一段,满足该段内可能温度不降

输入

第一行一个整数n。

接下来n行,每一行两个整数,表示l_i和r_i。

输出

接下来输出一个整数,表示答案。

输入样例

6
6 10
1 5
4 8
2 5
6 8
3 5

输出样例

4


2.思路

我先来说一说这道题的难点吧:但遇到一天的温度在你前面连续几天温度不降的基础上降低了,这时就只能在前面连续的一段中找一天的最低温度不高于这一天的最高温度,但是这样的话你还要把这个序列处理完,确保是一个不降的序列,才能继续往后走,自然就会超时。

既然难点已经抛出来了,平常的思路又解决不了,就想一想其他思路吧。我们要满足连续几天的温度不降,就要让这几天的最高温都大于这几天最高的最低温度,于是乎,我们就可以用一个单调下降队列来存每天的最低温度,队头就是最高温度。那么每天的温度一进去就要和对头比较,拿这一天的最高温度与对头比较,如果大于等于的话说明加上这一天也可以组成一个温度不降的序列,否则就要去掉对头元素,与更低的温度进行比较,直到可以组成一个不降序列。到了这里还没完,我们还没有处理这一天的最低温度呢!这次就是从队尾最低的最低温度开始,找到合适这一天的最低温度的位置,然后入队即可。在边维护队列时,可以边找出最长的温度连续不降的天数。


如此,单调队列的队头和队尾又完美的配合了一次,大将可能有点懵,请看一下思维图:


这下思路清晰了吧,附代码:

#include <cstdio>
#define max(a, b) a > b ? a : b
#define M 1000005
int n, l[M], r[M], tail, head, ans, dl[M], len;//dl为单调下降队列
int main (){
    scanf ("%d", &n);
    for (int i = 1; i <= n; i ++)
        scanf ("%d %d", &l[i], &r[i]);
    dl[++ tail] = ans = len = 1;
    head = tail;
    for (int i = 2; i <= n; i ++){//不用到n+1,因为len的计算方式是i - dl[head - 1]
        while (head <= tail && l[dl[head]] > r[i])//处理队头元素的最小温度要小于i的最大温度
            head ++;
        if(head > tail)
            len = 1;
        else
            len = i - dl[head - 1];
        while (head <= tail && l[i] > l[dl[tail]])//处理要达到的最小温度
            tail --;
        ans = max (ans, len);
        dl[++ tail] = i;
    }
    printf ("%d", ans);
    return 0;
}

练习:

好,理解了这道题,就来看重头戏,单调队列优化DP:

三.例题2:单调队列优化DP:猴子


1.题目

题目描述

有Q只猴子要从第一棵树到第n棵树去,第i只猴子一次跳跃的最远距离为Ki。如果它在第x棵树,那它最远可以跳到第x+Ki棵树。如果第j棵树的高度比第i棵树高或相等,那么它从第i棵树直接跳到第j棵树,它的劳累值会增加1。所有猴子一开始在第一棵树,请问每只猴子要跳到第n棵树花费的劳累值最小。

输入

第一行一个整数n,表示有n棵树。(2<=n<=1000000)

接下来第二行给出n个正整数D1,D2,……,Dn(1<=Di<=10^9),其中Di表示第i棵树的高度。

第三行给出了一个整数Q(1<=Q<=25),接下来Q行,给出了每只猴子一次跳跃的最远距离Ki(1<=Ki<=N-1)。

输出

输出Q行,每行一个整数,表示一只猴子的最小的劳累值。

输入样例 

9

4 6 3 6 3 7 2 6 5

5

 输出样例

2

1


2.思路 

这道题刚拿到,是不是不知道如何用单调队列?那就对了。我既然都说了单调队列优化DP,那么这道题的主体思路肯定是DP动态规划呀。现在就让我们来研究一下状态转移方程。

1.状态转移方程

只需定义一个一维的DP数组:dp[i]。它表示猴子跳到第i棵树需要花费的劳累值。那么,朴素的DP算法就是两重循环,第一重循环从1到n枚举猴子跳到每一棵树的最小劳累值,内层循环枚举在猴子的跳跃范围内,从哪棵树跳过来最划算。于是状态转移方程是不难想到:dp[i] = min (dp[i], dp[j] + (d[i] >= d[j]))(O_{n^{2}}).(d[i] >= d[j])是指当符合要求时返回1,否则返回0。

2.单调队列出场

但以上的DP朴素算法太耗时间了,我们不妨把dp数组放入单调上升队列中,每次取队头元素就是最优的那一个dp[j]。但是还有很多细节我们没有处理,共有以下几点:

①确保队头元素与i这棵树的距离不超过猴子能跳的最大距离。这个问题只需每次循环一开始就判断队头元素与i这棵树的距离,如果大于了猴子能跳的最远距离,就让对头元素出队。

②难道队头元素就是最优解吗?答案:是。可能有一种十分玄妙的情况:如果有一个元素的值与对头元素相同,且如果从那一个元素的那棵树跳到i不需要花费,而从队头元素跳到i需要花费,这就可以说明队头元素不一定是最优解呀?对于这种情况,我敢担保,一定不会出现,为何?请看下一步:

③处理队尾元素。第一种情况大家都想得到,就是当dp[i]小于队尾元素时,要让队尾元素出队;还有一种情况,就是回应刚才第二步的问题:当dp[i]等于队尾元素时,取高度最高的那一棵树,这样就能避免第二步中的质疑,保证队头元素不仅最小,还是最小的之中树高度最高的。

好了,我们就完成了一次简单的单调队列优化DP动态规划。


思维导图如下:


附代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define M 1000005
#define reg register
int n, d[M], Q, k, head, tail, a[M], dp[M];
inline void read (int &x){
    int f = 1; x = 0; char c = getchar();
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar();}
    x *= f;
}
int main (){
    read (n);
    for (reg int i = 1; i <= n; i ++)
        read (d[i]);
    read (Q);
    while (Q --){
        read (k);
        head = tail = 1;//预处理
        dp[1] = 0;
        a[tail] = 1;
        for (reg int i = 2; i <= n; i ++){
            while (i - k > a[head] && tail >= head)//处理队头
                head ++;
            dp[i] = dp[a[head]] + (d[i] >= d[a[head]]);//计算dp[i]
            while (tail >= head){//处理队尾元素
                if (dp[a[tail]] > dp[i] || (dp[a[tail]] == dp[i] && d[a[tail]] < d[i]))//两种情况
                    tail --;
                else
                    break;
            }
            a[++ tail] = i;
        }
        printf ("%d\n", dp[n]);
    }
    return 0;
}

四.总结

至此,我们已经掌握了简单的单调队列优化DP,知道了单调队列优化DP的基本思路,一定是先想出DP递推公式,再加以单调队列的优化。其实本章的第一题也是需要掌握的,因为要会灵活地运用单调队列,才能去优化DP动态规划。下期会难度加深哟!敬请期待。

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/85173267