[TJOI2009]开关 luogu链接:https://www.luogu.com.cn/problem/P3870
ps:好好的一道省选题打绿了不说,居然是道橙题!也就和一道dfs的难度一样···你洛谷是不是人均线段树??
分析题意:我们仍然是要进行区间修改的操作,那懒标记是逃不过了喂·
然后我们分析至少需要维护哪些信息:亮灯总个数是需要维护的吧
但是这样够不够?因为我们还要表示出“亮灯变暗,暗灯点亮”这样的操作,稍微一分析就可以发现总共的灯的数量就那么多,除了亮着的灯以外就是不亮的灯,因此我们可以用灯的总数size-亮灯/暗灯的个数sum来表示一次开关操作
然后为了让说明更详尽(多码点字数)
我们分析下需要改变哪些函数
build函数还是一如既往,但是pushup函数需要统计两个量:分别是灯的总数和亮着的灯的总数
重点是pushdown操作:之前我们在求区间和的时候懒标记是来记录需要给子区间传多少值的,因此只需要向下传递一次懒标记
这次也一样,我们可以直接把父节点的懒标记rev(表示的是操作开关的次数)直接顺延给子节点,然后给父节点的懒标记个数直接赋值为0就可
但是还有个细节需要处理,若是开关操作偶数次,我们实际上就不需要操作,因此我们对子节点操作的前提是rev是奇数
modify操作中若是要修改的区间包含了此区间,那么就要给rev的次数加一并且令sum = size - sum,表示暗灯变亮,亮灯变暗
query函数仍然是求和,没什么变化~
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200010;
struct node
{
int l,r;
int sum,size,rev; //rev是个懒标记 ,size是总共灯的个数,sum是亮着的灯的个数
}tr[N*4];
int n,m;
void pushup(int u) //每次更新父节点size和亮灯个数
{
tr[u].size = tr[u<<1].size + tr[u<<1|1].size;
tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}
void pushdown(int u) //由父节点更新子节点信息
{
node &root = tr[u],&l = tr[u<<1],&r = tr[u<<1|1];
l.rev += root.rev; r.rev += root.rev;
if(root.rev%2)
{
l.sum = l.size-l.sum;
r.sum = r.size-r.sum;
}
root.rev = 0;
}
void build(int u,int l,int r)
{
if(l==r) tr[u] = {l,r,0,1,0}; //刚开始默认灯就是黑的,所以总数为0,但是规模肯定都是1
else
{
tr[u] = {l,r};
int mid = l+r>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int l,int r)
{
if(tr[u].l>=l && tr[u].r<=r)
{
tr[u].sum = tr[u].size-tr[u].sum;//修改操作,每次让根节点的翻转次数+1
tr[u].rev++;
}
else
{
pushdown(u);
int mid = tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r);
if(r>mid) modify(u<<1|1,l,r);
pushup(u);
}
}
int query(int u,int l,int r)
{
if(tr[u].l>=l && tr[u].r<=r) return tr[u].sum;
else
{
pushdown(u);
int mid = tr[u].l+tr[u].r>>1;
int sum = 0;
if(l<=mid) sum += query(u<<1,l,r);
if(r>mid) sum += query(u<<1|1,l,r);
return sum;
}
}
int main()
{
cin>>n>>m;
build(1,1,n);
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
if(!x) modify(1,y,z);
else cout<<query(1,y,z)<<endl;
}
return 0;
}
要加油啊,干翻各种树!!