【笔记/模板】单调栈-单调队列

单调栈

定义

单调栈即满足单调性的栈结构。通过只在一端进出,从而维护一个数在前 / 后的第一个大于或小于它的数。

操作 / 方式

  1. 首先按照特定的顺序遍历。
  2. 如果要求出最小值,就要将栈顶大于入列元素的所有数全部弹出。
  3. 此时候的栈顶就是直到当前下标的答案。
  4. 加入这个元素并且继续更新。

代码

结构体 + STL模板

int a[N], ans[N];

struct Node { int val, idx; };
stack <Node> stk; 

signed main()
{
	// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n; cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = n; i >= 1; i--)
	{
		while (!stk.empty() && stk.top().val <= a[i]) stk.pop();
		ans[i] = (stk.empty() ? 0 : stk.top().idx);
		stk.push({a[i], i});
	}
	for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
	return 0;
}

手写栈 + 存下标

int a[N], ans[N];
int stk[N], top = 0;

signed main()
{
	// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n; cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = n; i >= 1; i--)
	{
		while (top && a[stk[top]] <= a[i]) top --;
		ans[i] = (top == 0 ? 0 : stk[top]);	
		stk[++ top] = i;
	}
	for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
	return 0;
}

单调队列

定义

一种通过两端弹出 / 推入 用来维护单调性质的队列,与普通队列不同,它可以用来维护区间最值问题。

原理

有一个长为 \(n\) 的序列 \(a\),以及一个大小为 \(k\)​ 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如,对于序列 \([1,3,-1,-3,5,3,6,7]\) 以及 \(k = 3\),有如下过程:

\[\def\arraystretch{1.2} \begin{array}{|c|c|c|}\hline \textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline \verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline \verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline \verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline \verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline \end{array} \]

如果维护最小值,那么单调队列中的操作如下:

操作 状态
\(1\) 入队。 \({1}\)
\(3\)\(1\) 大,\(3\) 入队。 \({1, 3}\)
\(-1\) 比所有元素都小,推出所有元素,\(-1\) 入队。 \({-1}\)
\(-1\) 比所有元素都小,推出所有元素,\(-3\) 入队。 \({-3}\)
\(5\)\(-3\) 大,有机会,入队。 \({-3, 5}\)
\(3\)\(5\) 小,将前面大于 \(3\) 的所有数全部推出,\(3\) 入队。 \({-3, 3}\)
\(-3\) 已经超过了窗口,所以 \(-3\) 出队,\(6\)\(3\) 大,\(6\) 入队。 \({3, 6}\)
\(7\)\(6\) 大,\(7\) 入队。 \({3, 6, 7}\)

操作方式

  1. 判断队首是否超出了边界。
  2. 在插入队尾前将不符合条件的元素全部推出。
  3. 在队尾插入新的元素。
  4. 此时对头便是以 \(i\)​​ 为终点的区间内最值答案。

代码

结构体 + STL

struct Node { int val, data; } a[N];
deque <Node> dqmax, dqmin;

int main()
{
	// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n, k; cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i].val, a[i].data = i;
	for (int i = 1; i <= n; i++)
	{
		while (!dqmin.empty() && dqmin.back().val >= a[i].val) dqmin.pop_back();
		dqmin.push_back(a[i]);
		if (dqmin.front().data == i - k) dqmin.pop_front();
		if (i >= k) cout << dqmin.front().val << ' ';  
	}
	cout << '\n';
	for (int i = 1; i <= n; i++)
	{
		while (!dqmax.empty() && dqmax.back().val <= a[i].val) dqmax.pop_back();
		dqmax.push_back(a[i]);
		if (dqmax.front().data == i - k) dqmax.pop_front();
		if (i >= k) cout << dqmax.front().val << ' ';  
	}
	return 0;
}

手写队列 + 存下标

int n, k;
int a[N];
int dqmin[N], dqmax[N];

void get_min()
{
	int hh = 1, tt = 0;
	for (int i = 1; i <= n; i ++)
	{
		while (hh <= tt && dqmin[hh] <= i - k) hh ++;
		while (hh <= tt && a[dqmin[tt]] > a[i]) tt --;
		dqmin[++ tt] = i;
		if (i >= k) cout << a[dqmin[hh]] << ' ';
	}
}

void get_max()
{
	int hh = 1, tt = 0;
	for (int i = 1; i <= n; i ++)
	{
		while (hh <= tt && dqmax[hh] <= i - k) hh ++;
		while (hh <= tt && a[dqmax[tt]] < a[i]) tt --;
		dqmax[++ tt] = i;
		if (i >= k) cout << a[dqmax[hh]] << ' ';
	}
}
posted @ 2024-11-04 10:47  ThySecret  阅读(105)  评论(0)    收藏  举报