1. 题目背景与需求分析
P3870 [TJOI2009] 开关是一道典型的信息学竞赛题目,主要考察选手对基础数据结构和区间操作的理解能力。题目描述了一个由n个灯组成的系统,每个灯有开和关两种状态,初始状态都是关闭的。需要处理两种操作:
- 将指定区间内的灯状态全部取反(开变关,关变开)
- 查询指定区间内亮着的灯的数量
这类题目在实际竞赛中非常常见,比如2013年NOI的一道类似题目就考察了相同的知识点。理解这类问题的关键在于高效处理区间更新和区间查询。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据结构选择与算法设计
2.1 线段树解决方案
线段树是解决这类区间问题的首选数据结构。我们需要设计每个节点存储以下信息:
- 区间范围[l, r]
- 当前区间内亮灯数量sum
- 懒惰标记lazy(表示是否需要取反)
线段树的构建时间复杂度为O(n),每次区间更新和查询的时间复杂度都是O(logn),完全满足题目要求。
cpp复制struct Node {
int l, r;
int sum; // 亮灯数量
int lazy; // 懒惰标记
} tree[MAXN * 4];
2.2 关键操作实现
2.2.1 建树操作
建树时需要初始化每个节点的亮灯数量为0(初始状态都是关闭的):
cpp复制void build(int p, int l, int r) {
tree[p].l = l;
tree[p].r = r;
tree[p].sum = 0;
tree[p].lazy = 0;
if (l == r) return;
int mid = (l + r) / 2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
}
2.2.2 下传懒惰标记
这是线段树实现中最关键的部分,需要正确处理标记的下传:
cpp复制void push_down(int p) {
if (tree[p].lazy) {
int l = p*2, r = p*2+1;
tree[l].sum = (tree[l].r - tree[l].l + 1)
