【洛谷题解/深基15.习9 P4387验证栈序列

本文讲解了如何通过模拟栈的出入栈操作来判断给定序列是否合法,介绍了栈的基本原理和在验证过程中的应用。代码示例展示了使用 C++ 实现的解决方案。

题目概况

链接: https://www.luogu.com.cn/problem/P4387
难度: 普及/提高-

题目分析

想一下,我们验证给定的栈序列的出入栈顺序是否合法时,是怎么做的?
是不是先入一个元素,如果栈顶元素按照顺序可以出,就按照出栈顺序,依次出栈.如果到最后这个栈出不完了(或者说非空),那么就是非法的出入栈顺序.
所以我们按照这个逻辑模拟一遍就行了.
涉及知识点为栈与模拟.

代码

#include <iostream>
#include <cstdio>
#include <stack>

using namespace std;
const int N = 1e5 + 5;

int q, pushed[N], poped[N];

int main() {
	scanf("%d", &q);
	while (q--) {
		int n, sum = 1; 
		stack <int> s;
		
		scanf("%d", &n);
		for (int i = 1; i <= n; i++) scanf("%d", &pushed[i]);
		for (int i = 1; i <= n; i++) scanf("%d", &poped[i]);
		
		for (int i = 1; i <= n; i++) {
			s.push(pushed[i]); //入栈
			while (!s.empty() && s.top() == poped[sum]) {
				s.pop();
				sum++;
			} 
		}
		if (!s.empty()) printf("No\n");
		else printf("Yes\n");
	}
	return 0;
}
<think>题目:P2824 [HEOI2016/TJOI2016] 排序 题目大意:给出一个1到n的全排列,有m个操作,每个操作将区间[l, r]升序或降序排序。最后询问第q个位置上的数字。 常规思路:直接模拟排序操作会超时,因为m和n最大为10^5。 高效解法:二分答案 + 线段树(或类似数据结构)维护01序列。 思路: 1. 注意到最后只询问一个位置的值,考虑二分答案ans。 2. 假设当前二分的值为mid,将原数组中大于等于mid的值设为1,小于mid的值设为0。 3. 这样,排序操作就变成了将区间内的0和1进行排序: - 升序排序:相当于将0全部放到前面,1全部放到后面。 - 降序排序:相当于将1全部放到前面,0全部放到后面。 4. 用线段树维护01序列,支持区间赋值和区间求和(即区间内1的个数)。 5. 对于每个操作[l, r]: - 先查询该区间内1的个数(记为cnt)。 - 如果是升序排序,则将区间[l, r-cnt]赋值为0,将区间[r-cnt+1, r]赋值为1。 - 如果是降序排序,则将区间[l, l+cnt-1]赋值为1,将区间[l+cnt, r]赋值为0。 6. 操作完成后,检查位置q的值是否为1(注意:由于我们二分的是答案,如果位置q为1,说明答案至少为mid,否则小于mid)。 7. 不断二分,直到找到最大的mid,使得最后位置q的值为1,这个mid就是答案。 代码实现步骤: - 二分框架:二分答案,范围是1到n。 - 构建线段树:支持区间赋值和区间求和。 - 对每个操作进行模拟(区间赋值操作)。 - 最后根据位置q的值调整二分边界。 注意:线段树需要支持区间覆盖(set)操作,并且要注意懒标记的处理。 代码示例: ```cpp #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 100005; struct Node { int sum; // 区间内1的个数 int set; // 懒标记:-1表示无操作,0表示置0,1表示置1 } tree[MAXN << 2]; struct Operation { int op, l, r; } op[MAXN]; int n, m, q; int a[MAXN]; // 原数组 int b[MAXN]; // 用于01序列的数组 void pushUp(int rt) { tree[rt].sum = tree[rt<<1].sum + tree[rt<<1|1].sum; } void pushDown(int rt, int l, int r) { if (tree[rt].set != -1) { int mid = (l + r) >> 1; // 下传左子树 tree[rt<<1].set = tree[rt].set; tree[rt<<1].sum = (mid - l + 1) * tree[rt].set; // 下传右子树 tree[rt<<1|1].set = tree[rt].set; tree[rt<<1|1].sum = (r - mid) * tree[rt].set; // 清除标记 tree[rt].set = -1; } } void build(int rt, int l, int r) { tree[rt].set = -1; if (l == r) { tree[rt].sum = b[l]; return; } int mid = (l + r) >> 1; build(rt<<1, l, mid); build(rt<<1|1, mid+1, r); pushUp(rt); } void update(int rt, int l, int r, int L, int R, int val) { if (L <= l && r <= R) { tree[rt].set = val; tree[rt].sum = (r - l + 1) * val; return; } pushDown(rt, l, r); int mid = (l + r) >> 1; if (L <= mid) update(rt<<1, l, mid, L, R, val); if (R > mid) update(rt<<1|1, mid+1, r, L, R, val); pushUp(rt); } int query(int rt, int l, int r, int L, int R) { if (L <= l && r <= R) { return tree[rt].sum; } pushDown(rt, l, r); int mid = (l + r) >> 1; int res = 0; if (L <= mid) res += query(rt<<1, l, mid, L, R); if (R > mid) res += query(rt<<1|1, mid+1, r, L, R); return res; } // 检查二分值mid:构建01序列,然后模拟所有操作 bool check(int mid) { // 构建01序列:>=mid为1,否则为0 for (int i = 1; i <= n; i++) { b[i] = (a[i] >= mid); } // 构建线段树 build(1, 1, n); // 模拟m个操作 for (int i = 0; i < m; i++) { int l = op[i].l, r = op[i].r; int cnt = query(1, 1, n, l, r); // 查询区间内1的个数 if (op[i].op == 0) { // 升序:0在前,1在后 if (cnt > 0) update(1, 1, n, r - cnt + 1, r, 1); // 后cnt个置1 if (r - l + 1 - cnt > 0) update(1, 1, n, l, r - cnt, 0); // 前面的置0 } else { // 降序:1在前,0在后 if (cnt > 0) update(1, 1, n, l, l + cnt - 1, 1); // 前cnt个置1 if (r - l + 1 - cnt > 0) update(1, 1, n, l + cnt, r, 0); // 后面的置0 } } // 查询位置q的值 return query(1, 1, n, q, q) == 1; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); } for (int i = 0; i < m; i++) { scanf("%d%d%d", &op[i].op, &op[i].l, &op[i].r); } scanf("%d", &q); // 二分答案 int L = 1, R = n, ans = 0; while (L <= R) { int mid = (L + R) >> 1; if (check(mid)) { ans = mid; L = mid + 1; } else { R = mid - 1; } } printf("%d\n", ans); return 0; } ``` §§相关问题§§ 1. 为什么二分答案的正确性成立?如何保证最终答案的正确性? 2. 线段树在本题中维护的信息是什么?懒标记的作用是什么? 3. 如果题目要求最后多个位置的值,如何修改算法? 4. 如何处理排序操作中的区间覆盖与线段树懒标记的冲突? 5. 是否有其他数据结构(如平衡树)可以替代线段树解决此题?</think>### 洛谷 P2824 "排序" C++ 题解 **题目大意**:给定一个 1~n 的排列,进行 m 次区间排序操作(升序或降序),最后询问第 q 个位置的值。 **核心算法**:二分答案 + 线段树维护 01 序列 **解题思路**: 1. **二分答案**:最终答案范围 [1, n],二分检查 mid 是否可能为答案 2. **01 序列转换**:将原数组转为 01 序列(≥mid 为 1,<mid 为 0) 3. **线段树操作**: - 排序操作转化为区间赋值(升序:0 在前 1 在后;降序:1 在前 0 在后) - 通过区间求和快速计算 1 的数量 4. **验证答案**:检查位置 q 在最终序列中是否为 1 **代码实现**: ```cpp #include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 1e5 + 5; struct Node { int sum; // 区间内 1 的数量 int set; // 懒标记:-1 无操作,0 置 0,1 置 1 } tree[MAXN << 2]; struct Operation { int op, l, r; // op=0 升序,op=1 降序 } ops[MAXN]; int n, m, q, pos; int a[MAXN], b[MAXN]; // 线段树维护 void pushUp(int rt) { tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum; } void pushDown(int rt, int len) { if (tree[rt].set != -1) { int setVal = tree[rt].set; tree[rt << 1].set = setVal; tree[rt << 1 | 1].set = setVal; tree[rt << 1].sum = setVal * (len - (len >> 1)); tree[rt << 1 | 1].sum = setVal * (len >> 1); tree[rt].set = -1; } } void build(int rt, int l, int r) { tree[rt].set = -1; if (l == r) { tree[rt].sum = b[l]; return; } int mid = (l + r) >> 1; build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r); pushUp(rt); } void update(int rt, int L, int R, int setVal, int l, int r) { if (L <= l && r <= R) { tree[rt].set = setVal; tree[rt].sum = setVal * (r - l + 1); return; } pushDown(rt, r - l + 1); int mid = (l + r) >> 1; if (L <= mid) update(rt << 1, L, R, setVal, l, mid); if (R > mid) update(rt << 1 | 1, L, R, setVal, mid + 1, r); pushUp(rt); } int query(int rt, int L, int R, int l, int r) { if (L <= l && r <= R) return tree[rt].sum; pushDown(rt, r - l + 1); int mid = (l + r) >> 1, res = 0; if (L <= mid) res += query(rt << 1, L, R, l, mid); if (R > mid) res += query(rt << 1 | 1, L, R, mid + 1, r); return res; } bool check(int mid) { // 构建 01 序列 for (int i = 1; i <= n; i++) b[i] = (a[i] >= mid); build(1, 1, n); // 执行所有排序操作 for (int i = 0; i < m; i++) { int l = ops[i].l, r = ops[i].r; int cnt = query(1, l, r, 1, n); // 查询 1 的数量 if (ops[i].op == 0) { // 升序排序 if (r - cnt + 1 <= r) update(1, r - cnt + 1, r, 1, 1, n); if (l <= r - cnt) update(1, l, r - cnt, 0, 1, n); } else { // 降序排序 if (l <= l + cnt - 1) update(1, l, l + cnt - 1, 1, 1, n); if (l + cnt <= r) update(1, l + cnt, r, 0, 1, n); } } return query(1, q, q, 1, n) == 1; // 检查目标位置 } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 0; i < m; i++) scanf("%d%d%d", &ops[i].op, &ops[i].l, &ops[i].r); scanf("%d", &q); // 二分答案 int L = 1, R = n, ans; while (L <= R) { int mid = (L + R) >> 1; if (check(mid)) { ans = mid; L = mid + 1; } else { R = mid - 1; } } printf("%d\n", ans); return 0; } ``` **算法优势**: - **时间复杂度**:O(m log² n) - **空间复杂度**:O(n) - **核心技巧**:将排序操作转化为 01 序列的区间赋值,避免直接排序的高复杂度
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oier_Asad.Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值