单调队列优化动态规划
引入
可用单调队列优化的动规有一大类题型,它们多半都有一个特征:
可以化归为序列中定长区间的最值问题。注意这里必须是定长区间,否则应用RMQ算法。
下面举一个例子:
-
输入:第一行两个正整数N(N<=600000),M。接下来一行N个数。
-
输出:对于每个区间[i,i-M+1],输出其中的最小值
-
思路:很显然这道题数据大到不允许利用RMQ的各种O(NlogN)的算法,想到每一次找最小值都只是将上一个区间后移一个数,即这个区间的答案很有可能可从上个区间获得,于是保持一个单调队列,区间每后移一次,就将num[i]插入队尾,若队尾的数Q[k]大于等于当前的数(num[i]),就说明Q[k]在以后就不可能是一个可行解,删去它,这样循环操作,直至Q[k]<num[i]为止。
对于每一个区间的最小值,只需输出当前队最前面的且在num中的下标号大于i-M+1的值即可。这样复杂度就从O(NlogN)飞跃到了O(N)。
此外,有很多问题可以化归为此问题进行解决,如求序列中长度不大于定值的最大连续子序列和等问题,并且此方法可以套用于满足上述性质的非动归题目,应用范围极广。
例题1 Diving the Path
POJ-2373
链接:http://poj.org/problem?id=2373
https://vjudge.net/problem/POJ-2373
题目大意
有一个长度为L(1000000)的区间,L是偶数,喷水的机器半径是a到b,
限制:1区间不能重复覆盖,2部分区域一定只能被一个机器覆盖。
求最少需要多少机器。
思路
f[i]表示长度为i的最少需要几个设备。
f[i]=1+min(f[i-2a]…f[i-2b]);
把f[i-2a]到f[i-2b]看成一个滑动窗口,维护一个从小到大的队列,左边可以出去,右边也可以出去,右边出去的条件是当前新加入的数小于等于队尾,就可以把队尾删除。
同时注意,由于某些区域只能由一个设备覆盖,所以对于[s,t]的这种区域,我们对于所有的s<i<t,都让f[i]=无穷大+1;
而且只有当i是偶数的时候,f[i]才有解。
q数组里面记录的是下标.
代码
#include <iostream>
#include <cstdio>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
using namespace std;
int const inf=(int)1e7;
int const N=1000005;
int a,b,n,l;
int f[N],q[N];
int main() {
scanf("%d%d%d%d",&n,&l,&a,&b);
re(i,1,l) f[i]=inf;
re(i,0,n-1) {
int x,y;
scanf("%d%d",&x,&y);
re(j,x+1,y-1) f[j]=inf+1;
}
f[0]=0;
int ft=0,lt=-1;
for(int i=2; i<=l; i+=2) {
while(ft<=lt && q[ft]<i-2*b) ft++;
if(i-2*a>=0) {
while(ft<=lt && f[q[lt]]>=f[i-2*a]) lt--;
q[++lt]=i-2*a;
}
if(f[i]==inf+1) continue;
if(ft<=lt) f[i]=1+f[q[ft]];
}
if(f[l]>=inf) f[l]=-1;
printf("%d\n",f[l]);
return 0;
}
例题2 最大子段和
最大子段和(最大子序和)
链接:
Luogu:https://www.luogu.com.cn/problem/P1115
51Nod:https://www.51nod.com/Challenge/Problem.html#problemId=1049
vjudge:https://vjudge.net/problem/51Nod-1049
思路
f[i]表示以i为结尾的最大子序和
维护一个单调队列即可。
代码
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long ll;
int const N=50005;
ll n;
ll a[N],f[N];
int main() {
scanf("%lld",&n);
for(int i=1; i<=n; i++) scanf("%lld",&a[i]);
for(int i=1; i<=n; i++) if(f[i-1]>0) f[i]=f[i-1]+a[i];
else f[i]=a[i];
ll ans=0;
for(int i=1; i<=n; i++) ans=max(f[i],ans);
printf("%lld\n",ans);
return 0;
}
例题3 绿色通道
CodeVS-3342(可惜CodeVS死了)
题目描述
《思远高考绿色通道》(Green Passage, GP)是唐山一中常用的练习册之一,其题量之大深受lsz等许多oiers的痛恨,其中又以数学绿色通道为最。2007年某月某日,soon-if (数学课代表),又一次宣布收这本作业,而lsz还一点也没有写……
高二数学《绿色通道》总共有n道题目要写(其实是抄),编号1…n,抄每道题所花时间不一样,抄第i题要花a[i]分钟。由于lsz还要准备NOIP,显然不能成天写绿色通道。lsz决定只用不超过t分钟时间抄这个,因此必然有空着的题。每道题要么不写,要么抄完,不能写一半。一段连续的空题称为一个空题段,它的长度就是所包含的题目数。这样应付自然会引起马老师的愤怒。马老师发怒的程度(简称发怒度)等于最长的空题段长度。
现在,lsz想知道他在这t分钟内写哪些题,才能够尽量降低马老师的发怒度。由于lsz很聪明,你只要告诉他发怒度的数值就可以了,不需输出方案。(快乐融化:那么lsz怎么不自己写程序?lsz:我还在抄别的科目的作业……)
输入描述
第一行为两个整数n,t,代表共有n道题目,t分钟时间。
以下一行,为n个整数,依次为a[1], a[2],… a[n],意义如上所述。
输出描述
仅一行,一个整数w,为最低的发怒度。
样例输入
17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5
样例输出
3
数据范围
60%数据 n<=2000
100%数据 0 < n <=50000,0 < a[i] <=3000,0< t<=100000000
思路
先二分枚举答案 枚举一个最大的空题区间mid,然后dp判断是否可以达到
F[i]表示第i题一定要做的情况下,前i题满足条件,最少需要花费的时间
F[i]=min(f[j])+a[i]; i-mid-1<=j<=i-1 单调队列
代码
CodeVS没了,代码也没了。
例题4
POJ-1276
链接:http://poj.org/problem?id=1276
https://vjudge.net/problem/POJ-1276
思路
用单调队列优化多重背包
多重背包状态转移方程:
有三重循环
二进制可以优化一下
通过单调队列可以优化到O(nm)
求f[i][0…m]
设余数为r,我们来求解f[i][r+k*w[i]];
r=0:
r=1:
代码
#include <cstdio>
#include <cstring>
using namespace std;
int const N=13;
int const M=100005;
int n,m;
int f[M],a[N],b[N],q[M],v[M];
int main() {
while(scanf("%d",&m)!=EOF) {
scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d%d",&b[i],&a[i]);
memset(f,0,sizeof(f));
for(int i=1; i<=n; i++) for(int r=0; r<a[i]; r++) {
int st=0,ed=-1,k=0;
for(int j=r; j<=m; j+=a[i],k++) {
while(st<=ed && q[st]<k-b[i]) st++;
while(st<=ed && v[ed]<=f[j]-k*a[i]) ed--;
q[++ed]=k,v[ed]=f[j]-k*a[i];
f[j]=v[st]+k*a[i];
}
}
printf("%d\n",f[m]);
}
return 0;
}
其他练习
-
POJ-1742
http://poj.org/problem?id=1742
https://vjudge.net/problem/POJ-1742 -
Vijos-1091
https://vijos.org/p/1091 -
HDU-1171
http://acm.hdu.edu.cn/showproblem.php?pid=1171
https://vjudge.net/problem/HDU-1171 -
POJ-2823
http://poj.org/problem?id=2823
https://vjudge.net/problem/POJ-2823 -
BZOJ-1855
https://www.lydsy.com/JudgeOnline/problem.php?id=1855 -
POJ-3017
http://poj.org/problem?id=3017
https://vjudge.net/problem/POJ-3017 -
LA-4327
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2328
https://vjudge.net/problem/UVALive-4327 -
POJ-2796
http://poj.org/problem?id=2796
https://vjudge.net/problem/POJ-2796 -
UVA-12170
https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3322
https://vjudge.net/problem/UVA-12170