洛谷题单指南-线段树-P3870 [TJOI2009] 开关
原题链接:https://www.luogu.com.cn/problem/P3870
题意解读:有n个数的序列,初始都是0,支持两种操作:将区间[l,r]内所有数异或1,求区间[l,r]内1个个数,输出所有求区间1的个数操作的结果。
解题思路:
灯的开关可以用0,1表示,改变灯的状态可以用异或操作,统计多少灯是开的就是计算1的个数,因此该题很容易进行转化。
现在要分析两个关键点:
1、计算区间1的个数是否具有可合并性?
显然,对于mid=(l+r)/2,区间[l,r]中1的个数 = [l,mid]中1的个数 + [mid+1,r]中1的个数,具有可合并行。
2、元素进行异或1是否具有可加性?
对一个开关tag,按一下异或1,状态是tag ^= 1,再按一下再异或1,状态同样tag ^= 1,具有可加性。
因此,此题可以用线段树解决,区间1的个数可以作为树节点的信息,当前开关的状态可以作为树节点的懒标记。
当更新节点并添加懒标记时:
void addtag(int u, int stat)
{
//对区间所有数异或1,则区间1的个数变成区间0的个数
tr[u].sum = (tr[u].r - tr[u].l + 1) - tr[u].sum;
//懒标记异或上1
tr[u].stat ^= stat;
}
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
struct Node
{
int l, r;
int sum; //区间[l,r]中1的个数
int stat; //懒标记,表示所有子节点的值都要异或stat
} tr[N * 4];
int n, m;
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r) tr[u].sum = 0, tr[u].stat = 0;
else
{
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void addtag(int u, int stat)
{
tr[u].sum = tr[u].r - tr[u].l + 1 - tr[u].sum;
tr[u].stat ^= stat;
}
void pushdown(int u)
{
if(tr[u].stat)
{
addtag(u << 1, tr[u].stat);
addtag(u << 1 | 1, tr[u].stat);
tr[u].stat = 0;
}
}
int query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
else if(tr[u].l > r || tr[u].r < l) return 0;
else
{
pushdown(u);
return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}
}
void update(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) addtag(u, 1);
else if(tr[u].l > r || tr[u].r < l) return;
else
{
pushdown(u);
update(u << 1, l, r);
update(u << 1 | 1, l, r);
pushup(u);
}
}
int main()
{
cin >> n >> m;
build(1, 1, n);
int op, x, y;
while(m--)
{
cin >> op >> x >> y;
if(op == 0) update(1, x, y);
else cout << query(1, x, y) << endl;
}
}