题目链接
题目描述
有一个初始为空的数列 。需要进行
次操作,每次操作在
的末尾添加一个正整数。每次操作结束后,需要找到当前数列
中所有后缀最大值的下标(从 1 开始),并输出这些下标的按位异或和。
一个下标 是后缀最大值,当且仅当
大于其后的所有元素,即对于所有
,
。
解题思路
本题要求在动态添加元素的过程中,高效地维护一个关于后缀最大值下标的统计量(异或和)。
1. 朴素思想与优化方向
一个简单的想法是,每次添加元素后,都从后往前重新扫描整个数组,找到所有的后缀最大值并计算异或和。设当前数组长度为 ,此操作需要
时间。
次操作的总时间复杂度将是
,对于
的数据规模来说,这显然会超时。
我们需要一个更高效的动态维护方法。关键在于分析当一个新元素 被添加到下标
时,后缀最大值的集合是如何变化的:
- 新元素
的后面没有其他元素,所以它自身一定是后缀最大值。因此,下标
总是新集合的一员。
- 对于一个旧的后缀最大值下标
,它要继续保持其地位,就必须大于它后面的所有元素,包括新加入的
。因此,旧的后缀最大值
能“存活”的条件是
。
这个特性——新来的元素 会淘汰掉所有值小于等于它的旧后缀最大值——是使用单调栈的经典信号。
2. 单调栈解法
我们可以维护一个单调栈,用来存储当前所有后缀最大值的 (下标, 值)
对。这个栈具有以下性质:
- 栈内元素:只有后缀最大值的
(index, value)
对。 - 单调性:从栈底到栈顶,元素的值是严格单调递减的。
同时,我们用一个变量 xor_sum
来实时追踪当前栈内所有元素下标的异或和。
算法流程:
当第 个新元素
(下标从 1 开始) 到来时:
-
出栈 (维护单调性): 从栈顶开始,循环判断。如果栈顶元素的值小于或等于新元素
,说明它不再是后缀最大值了。将其从栈顶弹出,并将其下标从
xor_sum
中“移除”(通过再次异或操作:xor_sum ^= popped_index
)。 -
入栈: 将新元素的
(i, x)
压入栈中。它现在是后缀最大值集合的一员。将其下标加入xor_sum
(xor_sum ^= i
)。 -
输出: 经过上述操作后,
xor_sum
的值就是当前所有后缀最大值下标的异或和。
复杂度分析:
每个元素最多入栈一次、出栈一次。因此,对于 次操作,总的时间复杂度为
,空间复杂度为
(最坏情况下,栈中会存储所有元素)。
代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
struct Node {
int index;
int value;
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int q;
cin >> q;
vector<int> a(q);
for (int i = 0; i < q; ++i) {
cin >> a[i];
}
stack<Node> s;
long long xor_sum = 0;
for (int i = 0; i < q; ++i) {
int current_val = a[i];
int current_idx = i + 1;
while (!s.empty() && s.top().value <= current_val) {
xor_sum ^= s.top().index;
s.pop();
}
s.push({current_idx, current_val});
xor_sum ^= current_idx;
cout << xor_sum << "\n";
}
return 0;
}
import java.util.Scanner;
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
static class Node {
int index;
int value;
Node(int index, int value) {
this.index = index;
this.value = value;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int q = sc.nextInt();
Deque<Node> s = new ArrayDeque<>();
long xorSum = 0;
for (int i = 1; i <= q; i++) {
int currentVal = sc.nextInt();
while (!s.isEmpty() && s.peekLast().value <= currentVal) {
xorSum ^= s.pollLast().index;
}
s.offerLast(new Node(i, currentVal));
xorSum ^= i;
System.out.println(xorSum);
}
}
}
import sys
def solve():
q_str = sys.stdin.readline()
if not q_str:
return
q = int(q_str)
a_str = sys.stdin.readline()
if not a_str:
return
a = list(map(int, a_str.split()))
s = [] # Monotonic stack: [(index, value)]
xor_sum = 0
for i in range(q):
current_val = a[i]
current_idx = i + 1
while s and s[-1][1] <= current_val:
xor_sum ^= s.pop()[0]
s.append((current_idx, current_val))
xor_sum ^= current_idx
sys.stdout.write(str(xor_sum) + '\n')
solve()
算法及复杂度
- 算法: 单调栈
- 时间复杂度: 数组中的每个元素最多被压入和弹出栈一次,因此总的时间复杂度是
。
- 空间复杂度: 栈中最多存储
个元素,因此空间复杂度为
。