先来看一下这道模板题:
https://www.luogu.org/problem/P2846
题目大意:
开始所有灯都是暗的,
改变:每次操作可以改变两盏灯之间所有灯的状态(亮变暗,暗变亮),
查询:每次查询两盏灯之间亮的灯的个数
这道题与线段树1中讲的那道题大部分都是一样的,只有一点,
那就是如何在线段树上区间修改
很明显,如果在线段树上执行n次单点修改,时间复杂度会达到O(n^2logn)
比朴素算法更劣,所以要找到更好的解决办法
我们可以在这棵树上的某些节点上标记lazy
表示这个节点对应的区间都要修改
但该节点的所有后代节点的num值都不修改
只修改当前节点的num值
但由于打上了lazy下次还能找到该节点
如果查询时遇到lazy标记的节点
就将标记下沉(取消该节点的lazy,并lazy标记上它的两个子节点,再修改它两个子节点的num值)
这样就能用O(logn)完美解决区间修改问题
再具体说一下这道题,这道题需要用上状态压缩
比如将
开、开、关、开、关、关
用二进制数110100来表示
变为十进制就是52,这样就可以用52表示 开、开、关、开、关、关这个状态
这样每个节点num存的就是它表示的区间中的状态对应的十进制数
最后,给大家看一下代码:
#include<bits/stdc++.h> using namespace std; int num[100000*4]; bool lazy[100000*4]; void build(int l,int r,int root) { if(l==r) { num[root]=0; return; } int mid=(l+r)/2; build(l,mid,root*2); build(mid+1,r,root*2+1); num[root]=num[root*2]+num[root*2+1]; } void change(int p,int q,int l,int r,int root) { //printf("%d %d %d %d %d ",p,q,l,r,root); if(p==l && q==r) { lazy[root]=1-lazy[root]; num[root]=r-l+1-num[root]; //cout<<num[root]<<" "<<root<<endl; return; } int mid=(l+r)/2; if(lazy[root]) { lazy[root*2]=1-lazy[root*2]; lazy[root*2+1]=1-lazy[root*2+1]; lazy[root]=0; num[root*2]=mid-l+1-num[root*2]; num[root*2+1]=r-mid-num[root*2+1]; } if(q<=mid) change(p,q,l,mid,root*2); else if(p>=mid+1) change(p,q,mid+1,r,root*2+1); else change(p,mid,l,mid,root*2),change(mid+1,q,mid+1,r,root*2+1); num[root]=num[root*2]+num[root*2+1]; } int search(int p,int q,int l,int r,int root) { if(p==l && q==r) { return num[root]; } int mid=(l+r)/2; if(lazy[root]) { lazy[root*2]=1-lazy[root*2]; lazy[root*2+1]=1-lazy[root*2+1]; lazy[root]=0; num[root*2]=mid-l+1-num[root*2]; num[root*2+1]=r-mid-num[root*2+1]; } if(q<=mid) return search(p,q,l,mid,root*2); else if(p>=mid+1) return search(p,q,mid+1,r,root*2+1); else return search(p,mid,l,mid,root*2)+search(mid+1,q,mid+1,r,root*2+1); } int main() { int n,w; scanf("%d%d",&n,&w); for(int i=1;i<=w;i++) { int t; int x,y; scanf("%d%d%d",&t,&x,&y); if(t==0) change(x,y,1,n,1); else if(t==1) printf("%d ",search(x,y,1,n,1)); } return 0; }
最后,再给大家推荐几到简单的养身模板题