酷代码 AI
菜单
服务商
DeepSeek字节豆包
更多选项

P3343 后缀最大值下标异或和:高效单调栈实现

好的,我们一起来分析这个问题。 ## 🧠 题目理解 题目要求: 有一个初始为空的数列,每次在末尾添加一个正整数。每次添加后,需要找出**所有后缀最大值**的下标,然后输出这些下标的按位异或(XOR)和。 **什么是后缀最大值下标?** 对于位置 `i`,如果它之后的所有元素都比它小,则它是后缀最大值。换句话说,从该位置到末尾的所有元素中,它是最大的。 **注意**: - 下标从 1 开始。 - 每次操作后,后缀最大值集合会变化,我们需要输出它们下标的异或和。 - 数据范围:`n ≤ 1e6`,`x` 可以非常大(`< 2^64`),所以必须用高效算法,O(n²) 会超时。 --- ## 🚀 高效算法思路 ### 核心想法 后缀最大值下标一定是**从右到左的递减序列**。例如数组 `[2, 1, 3, 5, 4]`,从右往左看: - 最后一个元素 `4` 是后缀最大值(下标5) - 往前看,`5 > 4`,所以 `5` 是后缀最大值(下标4) - 再往前,`3 < 5`,不是 - 再往前,`1 < 5`,不是 - 再往前,`2 < 5`,不是 所以后缀最大值下标是 `[4, 5]`。 但这只是最终结果,我们需要**每次插入后**都计算。 ### 关键观察 当我们从末尾添加一个新元素 `x` 时: - 新元素的下标是最大的(当前末尾),一定是后缀最大值。 - 它可能会导致前面某些元素不再是后缀最大值(如果它们 ≤ x)。 换句话说,我们需要**从后往前**删除所有值 ≤ x 的元素,因为新元素 x 比它们大或等于,它们就不再是后缀最大值。 ### 数据结构选择 我们需要一个**栈**来维护从右到左递减的序列(其实就是**单调栈结构**,但方向是反的)。 **具体步骤**: 1. 用一个栈 `stk` 存储当前所有后缀最大值的下标(从栈底到栈顶对应数组从左到右的顺序,但栈内元素对应的值必须严格递减)。 2. 每次插入新元素 `x`,下标 `idx`: - 先将 `idx` 入栈。 - 然后从栈顶检查:如果栈顶元素(下标)对应的值 ≤ x,就弹出它(因为它不再是后缀最大值)。 - 栈内所有元素就是当前的后缀最大值下标。 3. 输出栈内所有下标的异或和。 **注意**:我们需要快速知道某个下标对应的值。可以用一个数组存。 ### ⏱ 复杂度分析 - 每个元素最多入栈一次、出栈一次,总复杂度 O(n)。 - 每次询问需要输出栈内所有元素的异或和,如果每次都遍历栈,最坏情况会 O(n²)。 因此我们需要在弹出/入栈时动态维护异或和。 --- ## 💻 C++ 实现 ```cpp #include <bits/stdc++.h> using namespace std; using ull = unsigned long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<ull> a(n + 1); // 1-based 存储值 stack<int> stk; // 存储下标 ull xor_sum = 0; // 当前异或和 for (int i = 1; i <= n; ++i) { ull x; cin >> x; a[i] = x; // 新元素入栈 stk.push(i); xor_sum ^= i; // 从栈顶开始,移除值 <= x 的元素 // 注意:栈顶元素就是当前最后一个元素,但我们要检查的是"之前的栈顶" // 实际上我们只需要移除那些从后往前比 x 小的 // 但这里要注意:栈内元素是单调递减的,所以新元素可能移除前面多个 while (stk.size() >= 2) { int top_idx = stk.top(); stk.pop(); int second_idx = stk.top(); if (a[second_idx] <= x) { // second_idx 被移除 xor_sum ^= second_idx; stk.pop(); // 把 top_idx 重新放回去(因为它还在栈中?) // 等等,这里逻辑要重新想 } else { stk.push(top_idx); break; } } // 上面的循环逻辑有误,正确做法: // 我们需要检查的是:从后往前,如果某个元素的值 <= x,则移除它 // 栈中保存的是后缀最大值下标,从栈底到栈顶对应数组从左到右 // 新元素的下标是最大的,所以新元素入栈后,它前面的元素(栈中次顶)如果值 <= x,就移除 // 正确实现: } return 0; } ``` **等一下**,上面的逻辑有点混乱,我重新整理一下正确写法。 --- ### ✅ 正确实现(简洁版) ```cpp #include <bits/stdc++.h> using namespace std; using ull = unsigned long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<ull> a(n + 1); stack<int> stk; ull xor_sum = 0; for (int i = 1; i <= n; ++i) { cin >> a[i]; // 新元素必然成为后缀最大值(暂时) stk.push(i); xor_sum ^= i; // 检查新元素是否导致前面的后缀最大值失效 // 从栈顶往下看,新元素在栈顶,它前面的元素如果值 <= a[i],则被淘汰 // 但由于新元素在栈顶,我们检查栈中的 "下一个" 元素 // 正确做法:用一个临时变量记录被淘汰的元素 // 更简单:使用 while 循环检查栈顶下面一个元素 while (stk.size() >= 2) { int cur = stk.top(); stk.pop(); int prev = stk.top(); if (a[prev] <= a[i]) { // prev 被淘汰 xor_sum ^= prev; stk.pop(); // 把 cur 放回去 stk.push(cur); } else { stk.push(cur); break; } } cout << xor_sum << "\n"; } return 0; } ``` **但是**,上面的代码在逻辑上有缺陷: 新元素入栈后,我们应该**从栈中移除所有值 <= 新元素**的元素,而不是只检查一个。 ### ✅ 最终正确版本(推荐) ```cpp #include <bits/stdc++.h> using namespace std; using ull = unsigned long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<ull> a(n + 1); stack<int> stk; ull xor_sum = 0; for (int i = 1; i <= n; ++i) { cin >> a[i]; // 新元素入栈(暂时) stk.push(i); xor_sum ^= i; // 现在,检查并移除所有值 <= a[i] 的"前面"元素 // 注意:栈内保存的是递减序列,但新元素可能比前面某些大 // 我们需要从栈顶往下检查: // 栈顶是新元素,它下面的元素如果值 <= a[i],则被淘汰 while (stk.size() >= 2) { int top_idx = stk.top(); stk.pop(); // 暂时弹出新元素 int below_idx = stk.top(); // 检查它下面的元素 if (a[below_idx] <= a[i]) { // below_idx 被淘汰 xor_sum ^= below_idx; stk.pop(); // 移除被淘汰的元素 stk.push(top_idx); // 新元素放回去,继续检查下一个 } else { stk.push(top_idx); // 将新元素放回,结束 break; } } cout << xor_sum << "\n"; } return 0; } ``` **简化写法(更易理解)**: ```cpp #include <bits/stdc++.h> using namespace std; using ull = unsigned long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<ull> a(n + 1); stack<int> stk; ull xor_sum = 0; for (int i = 1; i <= n; ++i) { cin >> a[i]; // 新元素暂不考虑,先处理可能被它淘汰的元素 while (!stk.empty() && a[stk.top()] <= a[i]) { xor_sum ^= stk.top(); stk.pop(); } // 新元素成为新的后缀最大值 stk.push(i); xor_sum ^= i; cout << xor_sum << "\n"; } return 0; } ``` 这个版本逻辑清晰: - 先移除所有值 ≤ 新元素的栈内元素,因为它们不再是后缀最大值。 - 然后新元素入栈。 - 动态维护异或和。 --- ## 📘 举例验证 对于样例: ``` 5 2 1 3 5 4 ``` 过程: - i=1, a=2: 栈 [] -> 入栈 1,xor=1 → 输出1 - i=2, a=1: 栈 [1] -> 1≤1? 是,弹出1(xor=0) → 入栈2(xor=2) → 输出2?但样例输出是3,说明我的理解有误。 **检查**: 当 a=[2,1] 时,后缀最大值下标是 1,2? - 下标1:后面元素1<2 → 是后缀最大值 - 下标2:后面无元素 → 是后缀最大值 所以下标集合 {1,2},异或和 = 1 xor 2 = 3。正确。 所以我的算法应该输出3,而不是2。 问题出在哪里? 因为当新元素等于栈顶元素时(1≤1),应该弹出栈顶。但注意:题目要求**严格大于**,如果相等,新元素并不比前面的小,所以前面的元素依然是后缀最大值? 不对! 定义:`a_i > a_j`,所以如果相等,则不满足“大于”,所以前面的元素不再是后缀最大值。 因此当a=2,1时,下标1满足2>1,下标2无后续,所以两者都是。 但我的算法在i=2时,弹出栈顶1(因为a[1]=2≤1? 不成立),所以实际上没有弹出,然后入栈2,xor=1 xor 2=3。正确! 所以简化版代码是正确的。 --- ## 🧪 开发建议 1. **注意数据类型**:`x` 可能小于 2^64,用 `unsigned long long`。 2. **输入输出优化**:使用 `ios::sync_with_stdio(false)` 和 `cin.tie(nullptr)` 加速。 3. **栈与异或和**:动态维护异或和,避免每次遍历。 4. **边界情况**:当新元素非常大时,可能清空整个栈。 5. **测试建议**:构造极端数据(如严格递增、严格递减、全部相等)进行验证。 --- ## 📚 学习资源 - [单调栈详解 (OI Wiki)](https://oi-wiki.org/ds/monotonous-stack/) - [C++ STL stack 用法](https://en.cppreference.com/w/cpp/container/stack) - 异或运算性质:`x xor x = 0`,方便撤销操作。 --- **如果还有不理解的地方,欢迎继续提问!** ######[AI写代码神器 | 2721点数解答 | 2026-06-14 19:34:38]

相关提问
本站限时免费提问中
实用工具查看更多
Linux在线手册 [开发类]
Jquery在线手册 [开发类]
今日油价 [生活类]
图片互转base64 [开发类]
时间转换器 [开发类]