单调队列dp
其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的。
单调队列与普通队列不一样的地方就在于单调队列是双端队列,既可以从队首出队,也可以从队尾出队。
队列q[ ]存的是下标、 单调队列中元素大小单调 ; 队首元素必定是最值
单调队列的一般应用:
1.维护区间最值 2.优化DP
单调队列优化时间复杂度O(n)
例1:滑动窗口
有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
1≤k≤n≤10^6
!!!注意:数组开大点
#include<iostream>
#include<cstdio>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,k,a[5000005];
int q1[5000005],q2[5000005];
void minn(){
int h=1,t=0;
for(int i=1;i<=n;i++){
while(h<=t&&q1[h]<=i-k)h++;
while(h<=t&&a[i]<a[q1[t]])t--;
q1[++t]=i;
if(i>=k)printf("%d ",a[q1[h]]);
}
}
void maxx(){
int h=1,t=0;
for(int i=1;i<=n;i++){
while(h<=t&&q2[h]<=i-k)h++;
while(h<=t&&a[i]>a[q2[t]])t--;
q2[++t]=i;
if(i>=k)printf("%d ",a[q2[h]]);
}
}
int main(){
n=read();k=read();
for(int i=1;i<=n;i++)a[i]=read();
minn();puts("");
maxx();puts("");
return 0;
}
同样的裸题 https://www.luogu.com.cn/problem/P1440
只是把输出提前了...
#include<iostream>
#include<cstdio>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,k,a[5000005];
int q[5000005];
int main(){
n=read();k=read();
for(int i=1;i<=n;i++)a[i]=read();
int h=1,t=0;
for(int i=1;i<=n;i++){
printf("%d
",a[q[h]]);
while(h<=t&&q[h]<=i-k)h++;
while(h<=t&&a[i]<a[q[t]])t--;
q[++t]=i;
}
return 0;
}
例2:琪露诺
她只移动到区间[i+l,i+r]中的任意一格 ,且只能向右移动
从n+1-r到n都可以跳出
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N=5000005;
int n,L,R,a[N],dp[N],q[N],ans=-0x3f3f3f3f;
int main(){
n=read();L=read();R=read();
for(int i=0;i<=n;i++)a[i]=read();
int h=1,t=0,pos=0;
memset(dp,-0x3f,sizeof(dp));dp[0]=0;
for(int i=L;i<=n;i++){
while(h<=t&&dp[pos]>dp[q[t]])t--;
q[++t]=pos;
while(q[h]+R<i)h++;
dp[i]=dp[q[h]]+a[i];
pos++;
}
for(int i=n-R+1;i<=n;i++)
ans=max(ans,dp[i]);
printf("%d
",ans);
return 0;
}
例3:修剪草坪
FJ有N(1 <= N <= 100,000)只排成一排的奶牛,编号为1...N。每只奶牛的效率是不同的,奶牛i的效率为E_i(0 <= E_i <= 1,000,000,000)。
靠近的奶牛们很熟悉,因此,如果FJ安排超过K只连续的奶牛,那么,这些奶牛就会罢工开派对:)。因此,现在FJ需要你的帮助,计算FJ可以得到的最大效率,并且该方案中
没有连续的超过K只奶牛。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define LL long long
#define N 100005
int n,k,q[N],e[N];
LL ans=0,mn=99999999999999LL,f[N];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&e[i]),ans+=e[i];
int h=0,t=0;
for(int i=1;i<=n;i++){
f[i]=e[i]+f[q[h]];
while(h<=t&&f[i]<f[q[t]])t--;
q[++t]=i;
while(q[h]<i-k)h++;
}
for(int i=n-k;i<=n;i++)
mn=min(mn,f[i]);
printf("%lld",ans-mn);
return 0;
}
例4:旅行问题
Solution
将环给拆成一条链跑.
正反跑两次,维护路上前缀和,内容是每一个加油站的贡献,也就是p-d.如果路上最小的前缀和大于等于sum[i] 了,那么这种方式就可行,正反跑两遍就行.
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define LL long long
#define N 1000010
int n,q[N],h=1,t,p[N],d[N],ans[N<<1];
LL a[N<<1],sum[N<<1];
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i],&d[i]);
d[0]=d[n];
for(int i=1;i<=n;i++)
a[i]=a[i+n]=p[i]-d[i];
for(int i=1;i<2*n;i++)
sum[i]=sum[i-1]+a[i];
for(int i=1;i<2*n;i++) {
while(h<=t&&q[h]<=i-n)h++;
while(h<=t&&sum[q[t]]>=sum[i])t--;
q[++t]=i;
if(i>=n&&sum[q[h]]>=sum[i-n])ans[i-n+1]=1;
}
for(int i=1;i<=n;i++)
a[i]=a[i+n]=p[n-i+1]-d[n-i];
for(int i=1;i<2*n;i++)
sum[i]=sum[i-1]+a[i];
h=1,t=0;
for(int i=1;i<2*n;i++) {
while(h<=t&&q[h]<=i-n)h++;
while(h<=t&&sum[q[t]]>=sum[i])t--;
q[++t]=i;
if(i>=n&&sum[q[h]]>=sum[i-n])ans[2*n-i]=1;
}
for(int i=1;i<=n;i++){
if(ans[i])puts("TAK");
else puts("NIE");
}
return 0;
}
例5:跳房子
Solution
如果g个金币能满足条件,那么g+1个肯定也能,题目要求最大值最小,显然二分
然后暴力的做法(60pts)伪代码——
bool check(int g){
for(i:1~n)f[i]=-inf;
f[0]=0;
int l=min(d-g,1),r=d+g;
for(i:1~n)
for(j:i-1~1){
if(x[i]-x[j]<l)continue;
if(x[i]-x[j]>r)break;
f[i]=max(f[i],f[j]+w[i]);
if(f[i]>=k)return 1;
}
return 0;
}
然后 注意到 i 的区间( l,r )满足单调性,所以用单调队列优化
代码很多小细节
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define LL long long
const int N =500005;
const int inf=0x3f3f3f3f;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,d,k,s[N],q[N],x[N];
LL mx,f[N];
bool check(int g) {
for(int i=1;i<N;i++)f[i]=-inf,q[i]=0;
f[0]=0;
int l=1,r=0,j=0;
int R=d+g,L=max(1,d-g);//*1
for(int i=1;i<=n;i++) {
while(x[i]-x[j]>=L&&j<i) {
if(f[j]!=-inf){
while(l<=r&&f[q[r]]<=f[j])r--;
q[++r]=j;
}
j++;
}
while(l<=r&&x[i]-x[q[l]]>R)l++; //*2
if(l<=r) f[i]=f[q[l]]+s[i];
}
LL res=-inf;
for(int i=1;i<=n;i++)res=max(res,f[i]);
if(res>=k)return 1;
else return 0;
}
int main() {
n=read();d=read();k=read();
for(int i=1;i<=n;i++) {
x[i]=read();s[i]=read();
if(s[i]>0)mx+=s[i];
}
if(mx<k){
puts("-1");return 0;
}
int h=0,t=max(x[n],d),ans;//*3
while(h<=t){
int mid=(h+t)>>1;
if(check(mid)) {
ans=mid;//*4
t=mid-1;
} else {
h=mid+1;
}
}
printf("%d
",ans);
return 0;
}
例6:poj1821
蓝书好题P310
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=16010;
struct node{
int s,l,p;
bool operator < (const node &x) const {
return s<x.s;
}
}a[110];
int f[110][N],n,m,q[N];
int calc(int i,int k) {
return f[i-1][k]-a[i].p*k;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&a[i].l,&a[i].p,&a[i].s);
sort(a+1,a+1+m);
for(int i=1;i<=m;i++) {
int l=1,r=0;
for(int k=max(0,a[i].s-a[i].l);k<=a[i].s-1;k++) {
while(l<=r&&calc(i,q[r])<=calc(i,k)) r--;
q[++r]=k;
}
for(int j=1;j<=n;j++) {
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(j>=a[i].s) {
while(l<=r&&q[l]<j-a[i].l) l++;
if(l<=r) f[i][j]=max(f[i][j],calc(i,q[l])+a[i].p*j);
}
}
}
printf("%d
",f[m][n]);
return 0;
}