动态规划中的单调队列优化

最近经常出现单调队列,斜率优化的题目。看到周围的大神们都会做了,我只能跟上去。
要慢慢来,先学单调队列。

什么类型的DP需要用到常规的单调队列?

类似这样的转移方程可以用到单调队列:

f[i]=max(g[j])+w[i]

其中,g[j]是一个与i无关系的数。w[i]只与i有关系。

怎么用?

我们首先开一个队列。DP时:
1、先删掉前面超出范围的队头。
2、利用队头转移。
3、将这个数和队尾比较,若队尾不比它优,就删掉队尾,直到队列为空或队尾比它优。最后将它加进队尾。

原因

1、单调队列中的数都在要范围之内。
2、队头最优(不然早被后面的删掉了)。
3、为什么不只存队头?因为队头比较老,若超出范围就要被删掉。

例题 JZOJ 1771. 【NOIP动态规划专题】烽火传递

Description
  烽火台又称烽燧,是重要的军事防御设施,一般建在险要或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息;夜晚燃烧干柴,以火光传递军情,在某两座城市之间有n个烽火台,每个烽火台发出信号都有一定代价。为了使情报准确地传递,在连续m个烽火台中至少要有一个发出信号。请计算总共最少花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确传递。
Input
  第一行:两个整数N,M。其中N表示烽火台的个数,M表示在连续m个烽火台中至少要有一个发出信号。接下来N行,每行一个数Wi,表示第i个烽火台发出信号所需代价。
Output
  一行,表示答案。
Sample Input
5 3
1
2
5
6
2

Sample Output
4

Data Constraint
对于50%的数据,M≤N≤1,000 。 对于100%的数据,M≤N≤ 100,000,Wi≤100。

在单调队列的题中,这是一道水题了。
设f[i]表示i必须选时最小代价。
初值:
f[0]=0 f[1..n]=∞
方程:
f[i]=min(f[j])+w[i]
并且max(0,i-m)<=j
为什么j有这样的范围?如果j能更小,那么j~i这段区间中将有不符合条件的子区间,就会错。应保证不能有缝隙。
最后在f[n-m+1..n]中取最小值即答案
时间复杂度O(nm)

没有单调队列的代码

#include 
#include 
#include 
using namespace std;
#define I_O(x) freopen(""#x".in","r",stdin);freopen(""#x".out","w",stdout)
int n,m;
int w[100001];
int f[100001];
int main()
{
    I_O(beacon);
    scanf("%d%d",&n,&m);
    int i,j;
    for (i=1;i<=n;++i)
        scanf("%d",&w[i]);
    memset(f,127,sizeof f);
    f[0]=0;
    for (i=1;ifor (j=0;j//分两段写的原因是为了卡常——你也可以并在一起,然后j的初值为max(0,i-m)
            f[i]=min(f[i],f[j]);
        f[i]+=w[i];
    }
    for (i=m;i<=n;++i)
    {
        for (j=i-m;jint ans=0x7f7f7f7f;
    for (i=n-m+1;i<=n;++i)
        ans=min(ans,f[i]);
    printf("%d\n",ans);
}

我们发现这个方程可以以单调队列实现,用单调队列存好最优决策点,就可以将时间复杂度从O(nm)降到O(n)。

正解代码

#include 
#include 
#include 
using namespace std;
#define I_O(x) freopen(""#x".in","r",stdin);freopen(""#x".out","w",stdout)
int n,m;
int w[100001];
int que[100001],head=0,tail=0;
int f[100001];
int main()
{
    I_O(beacon);
    scanf("%d%d",&n,&m);
    int i,j;
    for (i=1;i<=n;++i)
        scanf("%d",&w[i]);
    memset(f,127,sizeof f);
    f[0]=0;
    que[0]=0;
    for (i=1;i<=n;++i)
    {
        if (que[head]//将超出范围的队头删掉
        f[i]=f[que[head]]+w[i];//转移(用队头)
        while (head<=tail && f[que[tail]]>f[i])
            --tail;//将不比它优的全部删掉
        que[++tail]=i;//将它加进队尾
    }
    int ans=0x7f7f7f7f;
    for (i=n-m+1;i<=n;++i)
        ans=min(ans,f[i]);
    printf("%d\n",ans);
}

你可能感兴趣的:(动态规划(DP))