单调队列——优化DP

队列元素保持单调递增(减),而保持的方式就是通过插队,把队尾破坏了单调性的数全部挤掉,从而使队列元素保持单调。
单调队列的作用 :优化DP。许多单调队列优化的DP可以使复杂度直接降维,下面就以最简单的一道题为例:

在某两座城市之间有 n 个烽火台,每个烽火台发出信号都有一定代价。为了使情报准确地传递,在连续 m 个烽火台中至少要有一个发出信号。请计算总共最少花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确传递。

Sample Input

5 3
1
2
5
6
2

Sample Output

4

分析题目,由于题目要求连续m个烽火台中至少要有一个发出信号,
很容易得出dp转移方程

F[i]=min(F[j]:i−m<j<i)+a[i]F[i]=min(F[j]:i−m<j<i)+a[i] 

最直接的方法是枚举状态,对于每一个i,我们在i-m+1到i-1中寻找一个最小的F[j]进行状态转移,枚举状态的时间复杂度是O(n),寻找最小值的状态时间复杂度是O(n),因此这种方法的复杂度是O(n^2)。题目的是数据范围是n<=100000,显然超时。
那么怎么用单调队列优化呢?

状态枚举到i,当m=4时,我们要做的就是在i-3到i-1中找到最小的F[j],那么枚举到i+1时,我们要做的就是要在i-2到i中找到最小的F[j]。上图中我们可以看出,要寻找最小值的区间向后移动了一位,也就是F[i-m+1]的值被抛弃,F[i-1]的值被加入。这里就可以用单调队列处理了,F[i-1]是插队的数据,F[i-1]有资格插队是因为它更优且更靠近i,比它更差的数将被它取代,保留那些数据没有任何好处。而那些已经不再维护区间之外的就不必再对其进行维护,出队即可。看了代码会更加明白:

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 100000;
int n, m, head = 1, tail = 0, ans = 2147483647;
int a[N + 1], f[N + 1], que[N + 1];

int main()
{
    freopen("beacon.in", "r", stdin);
    freopen("beacon.out", "w", stdout);

    scanf("%d%d", &n, &m);
    for (int i = 1;i <= n;i++)
        scanf("%d", &a[i]);
    for (int i = 1;i <= n;i++)
    {
        while (head <= tail && f[i - 1] <= f[que[tail]]) tail--; //当F[i-1]比队尾值更优时把队尾值弹出
        que[++tail] = i - 1; //把F[i-1]插入,这里插入下标而不插入值,便于从队头弹出
        while (head <= tail && que[head] < i - m) head++; //不属于区间维护内的数弹出
        f[i] = f[que[head]] + a[i]; //状态转移
    }
    for (int i = n;i > n - m;i--) //求出答案
        ans = min(ans, f[i]);
    printf("%d\n", ans);

    fclose(stdin);
    fclose(stdout);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/eternityZZing/article/details/81366053
今日推荐