入门题目:【模板】单调栈
https://www.luogu.com.cn/problem/P5788
一句话题题目意思:求每个元素
朴素的暴力算法是
想一想我们,一个一个读取数据,然后类似插入排序一样,在维护一个有序的序列
+-+
| | x
| | +-+
| | | |
| | | |
| | +-+ | |
| | | | +-+ | |
| | | | | | +-+ | |
| | | | | | | | | |
--+-+-+-+-+-+-+-+------+-+--
last
根据[[缩小放大法]],可以轻易的想到如果序列是一个单调下降的,那么每个一元素没有没有后续的"结果".
再给一个很高的柱子放在最后面,那么前面单调下降的所有的且小于
那么这些"投影"到last上的柱子就有了答案,就把可以它们去除了,最后变成如图下
+-+
| | x
| |+-+
| || |
| || |
| || |
| || |
| || |
| || |
--+-++-+--
last
- 建立一个栈
- 栈内准备添加一个位置last,值为x
- 所有比x小的栈,都出栈,并记录这些点的答案
- 入栈last
伪代码如下:
\begin{algorithm}
\begin{algorithmic}
\STATE Stack mysta;
\STATE int a[maxn];
\STATE int ans[maxn];
\FOR{$i=1$ \TO $n$}
\WHILE{$ a[mysta.top()] \leqslant a[i]$ \OR \NOT $mysta.empty()$ }
\STATE ans[mysta.top()] = i;
\STATE $a.pop()$
\ENDWHILE
\STATE $mysta.push(i)$;
\ENDFOR
\FOR{$i=1$ \TO $n$}
\Print ans[i];
\ENDFOR
\end{algorithmic}
\end{algorithm}
可以想到栈内的元素一定是单调不上升的.
时间负责度: 每个点最后入栈一次,出栈一次,所以时间为
核心思想: 去除那些已经得到答案的点,自然形成单调.
代码
/* author: Rainboy email: rainboylvx@qq.com
* time: 2023年 06月 12日 星期一 16:04:42 CST */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e6+5,maxe = 1e6+5; //点与边的数量
int n,m;
/* 定义全局变量 */
int a[maxn]; //每个位置的值
int ans[maxn]; //存答案
//栈的模板
template<typename T = int,int siz = maxn>
struct mystack{
T sta[siz+5];
int head = 0;
void clear() { head = 0;}
void push(T a) { sta[head++] = a;}
void pop(){head--;}
T top() { return sta[head-1];}
bool empty() { return head == 0;}
int size() { return head;}
};
mystack<int> sta; //创建一个栈
int main(int argc,char * argv[]){
cin >> n;
for(int i=1;i<=n;++i) cin >> a[i];
for(int i=1;i<=n;++i){
int t = a[i];
while( !sta.empty() && a[sta.top()] < t){
// 记录sta.top()位置投影到的位置
ans[sta.top()] = i;
sta.pop();
}
sta.push(i); //把位置入栈
};
//输出答案
for(int i=1;i<=n;++i) cout << ans[i] << " ";
std::cout << "\n";
return 0;
}
题目2: 直方图中最大的矩形
题目地址: [
roj 3032: Largest Rectangle in a Histogram」 直方图中最大的矩形]
解析
已知
- 由题目数据
知,算法应该是扫一遍就能得答案, - 设
表示可以整体问题可以转化成:
根据我一[[…/…/mind_theory/|放大缩小]]法的思维方式,
情况1: 高度一样
首先想到: 情况1: 如果所有的矩形的高度都一样,显然可以直接用矩形的数量乘以高度作为答案.这个问题变成十分简单.
情况2: 高度有序
情况2:矩形的高度从左到右单调增加.给每个矩形编号如下图
以
::: line
:::
那么以矩形
::: line
:::
当矩形
我们用如下的伪代码来描述我们的思想
\state w=0
\for{$i=4$ \TO $1$}
\state w++;
\state ans = max(ans,$w \times height_i$)
\endfor
\print ans
可以想到: 我们不停的从后向前扫描矩形,然后维护一个宽度为
于是我们得到一个结论:++当数据是有序时,我们可以轻松的解决题目++,时间
情况3: 数据是无序
根据集合的分类思想[[../../enumerationg_permutations/|pair_number],如果我们能++不漏++的(可以重复)求出所有的以第
x左边的矩形,是单调减的,这就是情况2.
以上图的
x右侧的比较x高的矩形,例如
如果
因为y后的单调增加,还是变成了情况2-1.在最后求出一个单调上长的矩形.
新出现的一个矩形
- 比前面的高
- 比前面的低
无论哪种情况,我们总是能转化为已知的操作来做.
以第i个矩形向右投影,就可以得到以第i个矩形为开头的面积了.
反过来思考,如果矩形是单调减的,那只要考虑以第i个矩形为结尾就可以了.
证明:按上面的操作,不漏:
即,问题
然后认识到这种操作就是栈上的操作,于是用了栈来维护.
核心思想:
- 截取
- 截取不会影响对后面的贡献
代码实现
/* author: Rainboy email: rainboylvx@qq.com
* time: 2023年 06月 12日 星期一 21:21:31 CST */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e6+5,maxe = 1e6+5; //点与边的数量
ll n,m;
ll a[maxn];
/* 定义全局变量 */
//栈的模板
template<typename T = int,int siz = maxn>
struct mystack{
T sta[siz+5];
int head = 0;
void clear() { head = 0;}
void push(T a) { sta[head++] = a;}
void pop(){head--;}
T top() { return sta[head-1];}
bool empty() { return head == 0;}
int size() { return head;}
};
struct node {
ll h; //高度
ll w; //宽度
};
//求矩形a的面积
ll area(node &a) {
return a.h * a.w;
}
mystack<node> sta; //创建一个栈
int main(){
while(1) {
cin >> n;
ll ans = -1;
if( n == 0 ) break;
sta.clear(); //清空栈
for(int i = 1;i <= n ;++i ) // 读取数据
cin >> a[i];
for(int i=1;i<=n;++i){
node t = {a[i],1};
//把比当前高的都删除
while(!sta.empty() && sta.top().h >= t.h )
{
t.w += sta.top().w;
sta.pop();
}
}
//清空sta里的值
std::cout << ans << "\n";
}
return 0;
}