最近经常出现单调队列,斜率优化的题目。看到周围的大神们都会做了,我只能跟上去。
要慢慢来,先学单调队列。
什么类型的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
2Sample Output
4Data 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<i
为什么j有这样的范围?如果j能更小,那么j~i这段区间中将有不符合条件的子区间,就会错。应保证不能有缝隙。
最后在f[n-m+1..n]中取最小值即答案
时间复杂度O(nm)
没有单调队列的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
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;i<m;++i)
{
for (j=0;j<i;++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;j<i;++j)
f[i]=min(f[i],f[j]);
f[i]+=w[i];
}
int ans=0x7f7f7f7f;
for (i=n-m+1;i<=n;++i)
ans=min(ans,f[i]);
printf("%d\n",ans);
}
我们发现这个方程可以以单调队列实现,用单调队列存好最优决策点,就可以将时间复杂度从O(nm)降到O(n)。
正解代码
#include <cstdio>
#include <cstring>
#include <algorithm>
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]<i-m)
++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);
}