单调数据结构
单调栈
例题1:Luogu5788
求解数组中元素右边第一个比它大的元素的下标。
由于要求解的是右边第一个大的元素,所以从右往左遍历。
维护一个从右到左递减的单调栈,最右是无限高。
每当有元素将要进栈,为了维护单调性,必须把单调栈中比它小的元素全部弹出。
因为它们已经没有用了,不可能被弹出的元素是某个元素右边第一个大的元素。
而此时在栈顶的元素便是答案,之后再把进栈的元素进栈。
归纳:当遍历到第k-1个元素时,k之前的元素构成单调栈:
k号元素出栈,l号元素是答案。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 3000005
#define inf 0x7f7f7f7f
#define Rep(i,a,b) for(int i=(a); i<=(b); ++i)
#define Per(i,a,b) for(int i=(a); i>=(b); --i)
int n,a[maxn],f[maxn];
stack<int> S;
int main() {
scanf("%d",&n);
Rep(i,1,n) scanf("%d",&a[i]);
Per(i,n,1) {
while(!S.empty()&&a[S.top()]<=a[i]) S.pop();
f[i]=S.empty()?0:S.top();
S.push(i);
}
Rep(i,1,n) printf("%d ",f[i]);
return 0;
}
例题2:POJ2559
朴素思路:对于每个矩形,枚举它左边右边能按它的高度最多延伸的长度,于是乘积得到答案。
运用单调栈,优化求出每个元素左边右边第一个比它小的元素下标,然后按朴素思路求解即可。
#include<cstdio>
#include<stack>
using namespace std;
#define ll long long
#define maxn 100001
#define inf 0x7f7f7f7f
#define Rep(i,a,b) for(int i=(a); i<=(b); ++i)
#define Per(i,a,b) for(int i=(a); i>=(b); --i)
int n,f[maxn],g[maxn];
ll a[maxn],res;
stack<int> S1,S2,clr;
int main() {
while(true) {
S1=S2=clr; res=0;
scanf("%d",&n); if(n==0) break;
Rep(i,1,n) scanf("%d",&a[i]);
Per(i,n,1) {
while(!S1.empty()&&a[S1.top()]>=a[i]) S1.pop();
f[i]=S1.empty()?n+1:S1.top();
S1.push(i);
}
Rep(i,1,n) {
while(!S2.empty()&&a[S2.top()]>=a[i]) S2.pop();
g[i]=S2.empty()?0:S2.top();
S2.push(i);
}
Rep(i,1,n) {
res=max(res,(f[i]-g[i]-1)*a[i]);
}
printf("%lld\n",res);
}
return 0;
}
另一种方法:只用一个单调栈
#include<algorithm>
#include<cstdio>
using std::max;
const int MAXN=1e5+1;
int n,a[MAXN],s[MAXN],w[MAXN],tp;
long long ans;
int main() {
while(true) {
scanf("%d",&n);
if(n==0) break;
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
a[n+1]=tp=ans=0;
for(int i=1; i<=n+1; i++) {
if(a[i]>a[s[tp]]) {
s[++tp]=i; w[i]=1;
} else {
int width=0;
while(a[s[tp]]>a[i]) {
width+=w[s[tp]];
ans=max(ans,(long long)width*a[s[tp]]);
tp--;
}
s[++tp]=i; w[i]=width+1;
}
}
printf("%lld\n",ans);
}
return 0;
}
例题3:Luogu2897
只设置一个递减的单调栈,但是要判断往左还是往右扩展。
利用两个指针指向左边和右边,哪边低就水往哪边流,单调栈就往哪边扩展。
代码实现:
#include<algorithm>
#include<cstdio>
using std::min;
const int MAXN=1e5+1;
int n,t,l,r,p,tp;
long long w[MAXN],h[MAXN],s[MAXN],f[MAXN],sum;
int main() {
scanf("%d",&n);
h[0]=h[n+1]=1e18;
for(int i=1; i<=n; i++) {
scanf("%lld%lld",&w[i],&h[i]);
if(h[i]<h[t]) t=i;
}
s[++tp]=t; l=t-1; r=t+1;
for(int i=1; i<=n; i++) {
int width=0;
p=h[l]<h[r]?l--:r++;
while(tp&&h[s[tp]]<h[p]) {
width+=w[s[tp]];
f[s[tp]]=sum+width;
sum+=(min(h[p],h[s[tp-1]])-h[s[tp]])*width;
tp--;
}
s[++tp]=p; w[p]+=width;
}
for(int i=1; i<=n; i++) printf("%lld\n",f[i]);
return 0;
}
单调队列
同理于单调栈。
例题1:Luogu1886
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 1000001
#define inf 0x7f7f7f7f
#define Rep(i,a,b) for(int i=(a); i<=(b); ++i)
#define Per(i,a,b) for(int i=(a); i>=(b); --i)
int n,m,Q1[maxn],Q2[maxn],a[maxn],head,tail;
int main() {
scanf("%d%d",&n,&m);
Rep(i,1,n) scanf("%d",&a[i]);
head=1; tail=0;
Rep(i,1,n) {
while(head<=tail&&Q1[head]+m<=i) head++;
while(head<=tail&&a[i]<a[Q1[tail]]) tail--;
Q1[++tail]=i;
if(i>=m) printf("%d ",a[Q1[head]]);
}
puts(""); head=1; tail=0;
Rep(i,1,n) {
while(head<=tail&&Q2[head]+m<=i) head++;
while(head<=tail&&a[i]>a[Q2[tail]]) tail--;
Q2[++tail]=i;
if(i>=m) printf("%d ",a[Q2[head]]);
}
puts("");
return 0;
}
唯一不同于单调栈的是,要先排除那些过时的元素从队头出队列,再把新的元素从队尾加入队列。
例题2:单调队列优化DP LuoguP1725
对于每个位置i,他可以由\(i-l+1\)到\(i-r+1\)更新。
于是用单调队列维护\(i-l+1\)到\(i-r+1\)的最大值来更新后面的。
#include<cstdio>
using std::max;
using std::swap;
const int MAXN=3e5+1;
int n,l,r,head=1,tail=0,a[MAXN],f[MAXN],q[MAXN],ans=-1e8,p;
int main() {
scanf("%d%d%d",&n,&l,&r);
if(l>r) swap(l,r);
for(int i=0; i<=n; i++) scanf("%d",&a[i]);
for(int i=1; i<=n; i++) f[i]=-1e8;
for(int i=l; i<=n; i++) {
while(head<=tail&&q[head]+r<i) head++;
while(head<=tail&&f[q[tail]]<=f[i-l]) tail--;
q[++tail]=i-l;
f[i]=max(f[i],a[i]+f[q[head]]);
if(i+r>n) ans=max(ans,f[i]);
}
printf("%d\n",ans);
return 0;
}
单调队列优化多重背包
\(f_{i,j}\)表示用了前 \(i\) 个物品用了 \(j\) 空间。
注意到
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=114514;
int n,m,f[2][N],v[N],w[N],s[N],q[N];
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) scanf("%d%d%d",&w[i],&v[i],&s[i]);
for(int j=0; j<=m; j+=w[1])
if(j/w[1]<=s[1]) f[1][j]=j/w[1]*v[1];
for(int i=2; i<=n; i++) {
for(int r=0; r<w[i]; r++) {
int head=1,tail=0;
for(int j=r; j<=m; j+=w[i]) {
while(head<=tail&&j-q[head]>w[i]*s[i]) head++;
while(head<=tail&&f[(i-1)%2][j]>=f[(i-1)%2][q[tail]]+(j-q[tail])/w[i]*v[i]) tail--;
q[++tail]=j;
f[i%2][j]=f[(i-1)%2][q[head]]+(j-q[head])/w[i]*v[i];
}
for(int i=0; i<=tail; i++) q[i]=0;
}
}
printf("%d\n",f[n%2][m]);
return 0;
}
复杂度\(O(nm)\)