P3870 [TJOI2009] 开关
开关问题 - 线段树解法
解题思路
这道题需要处理两种操作:区间状态翻转(开关灯)和区间开灯数量查询。线段树是解决这类区间操作问题的理想数据结构。
关键点分析:
-
状态翻转特性:每次翻转操作相当于对区间内每个灯的状态取反
-
高效统计:需要快速统计区间内开灯的数量
-
懒标记设计:使用标记记录是否需要翻转,避免频繁更新
解决策略:
-
使用线段树节点存储区间开灯数量和翻转标记
-
翻转操作时,开灯数量变为区间长度减去当前开灯数量
-
使用懒标记延迟翻转操作的传播
-
查询时维护正确的区间信息
代码注释
#include<bits/stdc++.h> #define lc rt * 2 // 左子节点索引 #define rc rt * 2 + 1 // 右子节点索引 #define lson lc,l,mid // 左子树参数 #define rson rc,mid+1,r // 右子树参数 #define f(i,s,e) for(int i = s; i <= e; i++) // 循环宏定义 #define ll long long // 定义long long类型别名 using namespace std; const int N = 1e6+10, inf = 0x3f3f3f3f; // 线段树节点结构体 struct node { ll sum, lazy; // sum:开灯数量,lazy:翻转标记(0无操作,1需要翻转) }; node t[N * 4]; // 线段树数组,4倍空间 ll n, m; // n:灯的数量,m:操作次数 // 执行翻转操作 void down(int rt, int l, int r) { // 翻转后开灯数量 = 区间长度 - 当前开灯数量 t[rt].sum = (r - l + 1) - t[rt].sum; // 翻转标记取反 t[rt].lazy = !t[rt].lazy; } // 标记下传函数 void pushdown(int rt, int l, int r) { if(t[rt].lazy == 0) return; // 无需操作 int mid = (l + r) / 2; down(lson); // 下传左子树 down(rson); // 下传右子树 t[rt].lazy = 0; // 清空当前节点标记 } // 信息上传函数 void pushup(int rt) { // 合并左右子树开灯数量 t[rt].sum = t[lc].sum + t[rc].sum; } // 区间翻转操作 void change(int rt, int l, int r, int x, int y) { if(r < x || y < l) return; // 区间无交集 if(x <= l && r <= y) { // 当前区间完全包含在目标区间内 // 执行翻转操作 t[rt].sum = (r - l + 1) - t[rt].sum; t[rt].lazy = !t[rt].lazy; return; } int mid = (l + r) / 2; pushdown(rt, l, r); // 下传标记 change(lson, x, y); // 处理左子树 change(rson, x, y); // 处理右子树 pushup(rt); // 更新当前节点 } // 区间查询操作 ll query(int rt, int l, int r, int x, int y) { if(r < x || y < l) return 0; // 区间无交集 if(x <= l && r <= y) return t[rt].sum; // 直接返回开灯数量 int mid = (l + r) / 2; pushdown(rt, l, r); // 下传标记 // 返回左右子树查询结果之和 return query(lson, x, y) + query(rson, x, y); } int main() { scanf("%lld%lld", &n, &m); // 读取灯的数量和操作次数 f(i,1,m) { int op, x, y; scanf("%d%d%d", &op, &x, &y); // 读取操作 if(op == 0) { // 区间翻转操作 change(1,1,n,x,y); } else if(op == 1) { // 区间查询操作 printf("%lld\n", query(1,1,n,x,y)); } } return 0; }

浙公网安备 33010602011771号