单调数据结构

单调栈

例题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)\)

posted @ 2022-08-13 13:30  s1monG  阅读(45)  评论(0编辑  收藏  举报