栈是一种特殊线性表,其特点是后进先出或先进后出
在代码实现中,可以用数组+变量手写栈,也可以调用STL库中的栈容器
栈是维护数据局部关系的一种数据结构,单调栈则是对这一属性的极致应用
简单来说,单调栈就是栈中的元素满足单调关系(单调上升,单调下降,单调不上升,单调不下降)
举个例子,假设要构造个单调上升的栈,那么先压入第一个数据后,如果下一个数据大于栈顶元素,就将这个数据压入栈中;如果下一个数据小于等于栈顶元素,那么就不断弹出栈顶元素,然后接着判断该元素与新的栈顶元素的关系(开一个while循环,条件为栈非空且该元素小于等于栈顶元素,循环体为不断弹出栈顶元素)
在做题时,要根据题目描述判断如何构造单调栈,同时要注意栈中元素之间的关系(累加等),同时也可以利用单调关系遍历栈中的元素
例题乱头发节 题目描述贤正的某 N 头奶牛 (1 <= N <= 80,000) 正在过乱头发节!由于每头牛都 意识到自己凌乱不堪的发型, 宁智贤希望统计出能够看到其他牛的头发的牛的数量。
每一头牛 i有一个高度 h[i] (1 <= h[i] <= 1,000,000,000)而且面向东方排成 一排(在我们的图中是向右)。因此,第i头牛可以看到她前面的那些牛的头, (即i+1, i+2,等等),只要那些牛的高度严格小于她的高度。
例如这个例子:
牛#1 可以看到牛 #2, 3, 4的发型 ;牛#2 不能看到任何牛的发型; 牛#3 可以看到牛#4的发型; 牛#4 不能看到任何牛的发型 ;牛#5 可以看到牛#6的发型 ;牛#6 不能看到任何牛的发型!
让 c[i] 表示第i头牛可以看到发型的牛的数量;请输出 c[1] 至 c[N]的和。 如上面的这个例子,正确解是3 + 0 + 1 + 0 + 1 + 0 = 5。
输入格式输出格式
Line 1: 牛的数量 N。
Lines 2..N+1: 第 i+1 是一个整数,表示第i头牛的高度。
Line 1: 一个整数表示c[1] 至 c[N]的和。
样例数据input
6 10 3 7 4 12 2output
5数据规模与约定时间限制:1texts
空间限制:256textsMB
每只牛都是向右侧看,它所看到的牛必须严格小于它的高度,并且牛与牛之间的高度关系是局部关系,所以考虑维护一个单调下降的单调栈
#includeusing namespace std; int n; long long a[90000]; long long ans; stack st; int main() { cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) { while(!st.empty()&&a[i]>=st.top()) { st.pop(); } st.push(a[i]); ans+=st.size()-1; } cout<
二、单调队列
队列也是一种特殊的线性表,它和栈不同,它是允许在一段进行插入,在另一端进行删除的线性表。允许插入的一端称为队尾,通常用一个队尾指针tail指向队尾元素,即tail总是指向最后被插入的元素;允许删除的一端称为队首,通常也用一个队首指针head指向排头元素的前面。
队列的特点是先进先出,主要维护数据的整体顺序,一旦数据进入队列,其出队顺序就固定下来,即进队序列一旦是确定的,出队序列也是唯一确定的,这一点和栈是不同的。
队列可以有单向操作的queue,也有可双端操作的deque(这两种STL库中都提供),双端的deque的两端都可以进行插入删除操作,函数调用也和queue大同小异,只是需要点明插入或删除的位置
下面给出两种队列常用的基础函数,如果有兴趣学习更多可以自行搜索
单端队列: queueque; que.push(a);//插入操作 que.pop();//删除操作 que.front();//取出队首元素 que.empty();//判断队列是否为空 que.size();//返回队列元素个数 / 双端队列: deque que; que.push_front(a);//队首插入元素 que.push_back(a);//队尾插入元素 que.pop_front();//删除队首元素 que.pop_back();//删除队尾元素 que.empty();//判断队列是否为空 que.size();//返回队列元素个数
同单调栈一样,单调队列也在维护数据间的单调关系,但其维护的更侧重于整体的关系,如单调栈维护两两数据间的大小关系,而单调队列则在寻找一段区间内的极值时常用 。
例题 题目描述给你一个长度为 N 的数组,一个长为 K 的滑动的窗体从最左移至最右端,你只能见到窗口的K个数,每次窗体向右移动一位,如下表:
你的任务是找出窗口在各位置时的 max value,min value.
输入格式第 1 行 n,k,
第 2 行为长度为 n 的数组
输出格式2 行,
第 1 行每个位置的 min value,
第 2 行每个位置的 max value
样例数据input
8 3 1 3 -1 -3 5 3 6 7output
-1 -3 -3 -3 3 3 3 3 5 5 6 7数据规模与约定20%: n<=500;
50%: n<=100000;
100%: n<=1000000;
时间限制:1texts
空间限制:256textMB
窗口是可以滑动的,并且有一定的长度限制,那么我们就可以把它看作一个有长度限制的队列
在本题中,我们要寻找窗口内的最大值和最小值,那么我们就可以构造两个单调栈:当求最小值时,构造一个单调上升的队列,那么该窗口内的最小值就始终是队列的队首元素;当求最大值时,构造一个单调下降的队列,那么该窗口内的最大值就始终是队列的队首元素。
然后在考虑如何维护区间的长度限制:当队首元素的下标与队尾元素的下标相差超过该长度限制时,就将队首元素弹出,以保持区间的长度限制
那么在这道题的操作中,我们发现:在构造单调队列时,我们需要在队尾同时弹出和插入元素,在维护区间长度时,我们需要从队首弹出元素,所以这道题我们运用双端队列deque进行操作。
#includeusing namespace std; struct node { int num,pla; }window[1010000]; int n,k; deque que1,que2; void init() { cin>>n>>k; for(int i=1;i<=n;i++) { cin>>window[i].num; window[i].pla=i; } } void minn()//求最小值 { que2.push_back(window[1]); for(int i=2;i<=n-1;i++) { while(!que2.empty()&&window[i].num =k) cout< que1.back().num)//构造单调下降队列 { que1.pop_back(); } que1.push_back(window[i]); if(que1.front().pla =k) cout< que1.back().num)//最后一个答案后没有空格,单独处理一次 { que1.pop_back(); } que1.push_back(window[n]); if(que1.front().pla



