[TJOI2009]开关 (线段树)
题目描述
现有N(2 ≤ N ≤ 100000)盏灯排成一排,从左到右依次编号为:1,2,......,N。然后依次执行M(1 ≤ M ≤ 100000)项操作,操作分为两种:第一种操作指定一个区间[a, b],然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开),第二种操作是指定一个区间[a, b],要求你输出这个区间内有多少盏灯是打开的。灯在初始时都是关着的。
输入输出格式
输入格式:
第一行有两个整数N和M,分别表示灯的数目和操作的数目。接下来有M行,每行有三个整数,依次为:c, a, b。其中c表示操作的种类,当c的值为0时,表示是第一种操作。当c的值为1时表示是第二种操作。a和b则分别表示了操作区间的左右边界(1 ≤ a ≤ b ≤ N)。
输出格式:
每当遇到第二种操作时,输出一行,包含一个整数:此时在查询的区间中打开的灯的数目。
输入输出样例
输出样例#1:
1
2
Solution
很典型的线段树区间反转.
即用线段树维护每一个点的操作次数,如果%2==1 那么就是开着的.
否则,则为关着的.
不过这个版本的是用的结构体储存了一下每个线段树节点的关的点数和开得点数.
如果需要操作就把当前开的和关的数量交换.
然后就是基本的操作.
#include<bits/stdc++.h> #define maxn 100005 #define ll(x) x*2 #define rr(x) x*2+1 using namespace std; struct tree{ int l,r,s0,s1,f; }t[maxn*4]; int n,m; int read() { int x=0; char ch=getchar(); while (!isdigit(ch)) ch=getchar(); while(isdigit(ch)) x=x*10+ch-'0',ch=getchar(); return x; } void build(int x,int l,int r) { t[x]=(tree){l,r,r-l+1,0,0}; if (l==r) return; int mid=l+r>>1; build(ll(x),l,mid),build(rr(x),mid+1,r); } void push_down(int x) { swap(t[ll(x)].s0,t[ll(x)].s1); swap(t[rr(x)].s0,t[rr(x)].s1); t[x].f^=1,t[ll(x)].f^=1,t[rr(x)].f^=1; } void mdfy(int x,int l,int r) { if (t[x].l>r||t[x].r<l) return; if (t[x].l>=l&&t[x].r<=r){ t[x].f^=1,swap(t[x].s0,t[x].s1); return; } if (t[x].f) push_down(x); mdfy(ll(x),l,r),mdfy(rr(x),l,r); t[x].s0=t[ll(x)].s0+t[rr(x)].s0; t[x].s1=t[ll(x)].s1+t[rr(x)].s1; } int srch(int x,int l,int r){ if (t[x].l>r||t[x].r<l) return 0; if (t[x].l>=l&&t[x].r<=r) return t[x].s1; if (t[x].f) push_down(x); return srch(ll(x),l,r)+srch(rr(x),l,r); } int main(){ n=read(),m=read(),build(1,1,n); while (m--){ int f=read(),l=read(),r=read(); if (f) cout<<srch(1,l,r)<<endl; else mdfy(1,l,r); } return 0; }