P3870 [TJOI2009] 开关

开关问题 - 线段树解法

解题思路

这道题需要处理两种操作:区间状态翻转(开关灯)和区间开灯数量查询。线段树是解决这类区间操作问题的理想数据结构。

关键点分析:

  1. 状态翻转特性:每次翻转操作相当于对区间内每个灯的状态取反

  2. 高效统计:需要快速统计区间内开灯的数量

  3. 懒标记设计:使用标记记录是否需要翻转,避免频繁更新

解决策略:

  • 使用线段树节点存储区间开灯数量和翻转标记

  • 翻转操作时,开灯数量变为区间长度减去当前开灯数量

  • 使用懒标记延迟翻转操作的传播

  • 查询时维护正确的区间信息

代码注释

#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;
}

 

posted @ 2025-05-27 15:17  CRt0729  阅读(19)  评论(0)    收藏  举报