目录
单点操作
#include <iostream>
using namespace std;
const int MAX_N=10010;
int s[4*MAX_N];
void up(int p){
s[p]=s[p*2]+s[p*2+1];
}
void modify(int p,int l,int r,int x,int v){
//节点x上的权值增加v;
if(l==r){
s[p]+=v;
return ;
}
int mid=(l+r)/2;
if(x<=mid){
modify(p*2,l,mid,x,v);
}else{
modify(p*2+1,mid+1,r,x,v);
}
up(p);
}
int query(int p,int l,int r,int x,int y){
//p,l,r为当前更新到的节点,左右端点,x,y为要查询的区间
if(x<=l&&r<=y){
return s[p];
}
int mid=(l+r)/2,res=0;
if(x<=mid){
res+=query(p*2,l,mid,x,y);
}
if(y>mid){
res+=query(p*2+1,mid+1,r,x,y);
}
return res;
}
int main() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
int d;
cin>>d;
modify(1,1,n,i,d);
}
int q;
cin>>q;
while(q--){
int d,x,y;
cin>>d>>x>>y;
if(d==0){ //x处增加y
modify(1,1,n,x,y);
}else{//询问x-y的和
cout<<query(1,1,n,x,y)<<endl;
}
}
return 0;
}
Lazy标记
1.集中更新和动态统计子序列中的数据。
(1)子序列增加或者减少一个值
(2)子序列求和
例:POJ3468
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
struct node{
ll mark,sum; //线段树,其中节点i的数和为sum,懒惰标记为mark
}tree[maxn*4];
int x[maxn]; //初始值序列。
int n,m; //数字个数,操作次数。
void update(int l,int r,int i){ //标记法维护线段树(根为i,对应区间为[l,r])
if(!tree[i].mark) return; //若结点i未被标记,则退出,否则左右儿子对应的区间应该被完全覆盖,分别计算左右子区间的数和,并将节点i的标记下传给左右儿子。
int mid=l+r>>1;
tree[i<<1].sum+=tree[i].mark*(ll)(mid-l+1);
tree[i<<1|1].sum+=tree[i].mark*(ll)(r-mid);
tree[i<<1].mark+=tree[i].mark;
tree[i<<1|1].mark+=tree[i].mark;
tree[i].mark=0; //撤去标记
}
ll query(int tl,int tr,int l,int r,int i){ //计算线段树根为i,对应区间为[l,r]内子区间[tl,tr]的数字和。
if(tl>r||tr<l) return 0;
if(tl<=l&&r<=tr) return tree[i].sum; //标记法维护
update(l,r,i);
int mid=l+r>>1;
return query(tl,tr,l,mid,i<<1)+query(tl,tr,mid+1,r,i<<1|1);
}
void add_value(int tl,int tr,int l,int r,int i,int val){ //线段树根为i,对应区间为[l,r]内子区间[tl,tr]每个数+val
if(tl>r||tr<l) return ;
if(tl<=l&&r<=tr){
tree[i].sum+=val*(ll)(r-l+1);
tree[i].mark+=val;
return;
}
update(l,r,i);
int mid=(l+r)>>1;
add_value(tl,tr,l,mid,i<<1,val);
add_value(tl,tr,mid+1,r,i<<1|1,val);
tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
}
void build_tree(int l,int r,int i){ //构建以i为根,对应区间[1,n]的线段树。
if(l==r){
tree[i].sum=x[l];
return;
}
int mid=l+r>>1;
build_tree(l,mid,i<<1);
build_tree(mid+1,r,i<<1|1);
tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
}
memset(tree,0,sizeof tree);
build_tree(1,n,1);
while(m--){
char ch;
int l,r,v;
scanf(" %c",&ch);
if(ch=='Q'){ //询问[l,r]的区间和。
scanf("%d%d",&l,&r);
ll ans=query(l,r,1,n,1);
printf("%lld\n",ans);
}else{ //[l,r]中每个数均加上v
scanf("%d%d%d",&l,&r,&v);
add_value(l,r,1,n,1,v);
}
}
return 0;
}
2.计算可见线段
被插入的线段按照先后顺序,后面覆盖前面的,计算最终线段并区间中的可见线段数。
注意可能用到离散化。
例POJ 2528
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e4+10;
//线段i表示颜色i
bool tab[maxn]; //颜色码k被使用的标志是tab[k]
int l[maxn],r[maxn],x[maxn*3],num[maxn*3],tree[maxn*12];
/*
对于第i张海报来说,不大于左边界的不同坐标数为l[i],不大于右边界的不同坐标数为r[i],左边界坐标为x[3*i-2],右边界坐标为x[3*i-1];
中间位置坐标为x[3*i],x[]排序后x[1...j]中不重复的坐标数为num[j];
线段树中的节点k标记为tree[k],即代表区间的颜色码。
*/
void update(int i){ //标记法维护线段树。
if(!tree[i]) return ;
tree[i<<1]=tree[i<<1|1]=tree[i];
tree[i]=0;
}
void change(int tl,int tr,int l,int r,int i,int co){
if(tr<l||tl>r) return;
if(tl<=l&&r<=tr){
tree[i]=co;
return ;
}
update(i);
int mid=l+r>>1;
change(tl,tr,l,mid,i<<1,co);
change(tl,tr,mid+1,r,i<<1|1,co);
}
int query(int l,int r,int i){
if(tree[i]){
if(!tab[tree[i]]){
tab[tree[i]]=1;
return 1;
}
return 0;
}
if(l==r) return 0; //当前点未覆盖,则返回0
int mid=l+r>>1;
return query(l,mid,i<<1)+query(mid+1,r,i<<1|1);
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
int n,cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
x[++cnt]=l[i],x[++cnt]=r[i],x[++cnt]=(l[i]+r[i])/2;
//因为是区间,所以直接离散化的话可能导致可看见的区间数减少。所以添加一个中间值进行离散化。
}
sort(x+1,x+1+cnt);
cnt=unique(x+1,x+cnt+1)-(x+1);
memset(tree,0,sizeof tree);
for(int i=1;i<=n;i++){
int ll=lower_bound(x+1,x+1+cnt,l[i])-x;
int rr=lower_bound(x+1,x+1+cnt,r[i])-x;
change(ll,rr,1,cnt,1,i);
}
memset(tab,0,sizeof tab);
int ans=query(1,3*n,1);
printf("%d\n",ans);
}
return 0;
}
3.互不相交线段的更新与维护。
每次给出插入线段的长度l,若线段树中存在空位置数不小于l的子区间,则该线段插入(一般规定选择区间的优先级),可使树中满的线段是互不相交的,对删除操作,若线段树中存在被删线段的“满区间”,则该线段可被删除。
节点的懒惰标记一般包括:
(1)对应子区间的占据情况mark:分“全满”,“全空”,“部分占据”三部分。
(2)对应子序列的最长空区间lm,pos,即pos位置开始,长度为lm的区间为最长空区间。
(3)左端 最长空区间的长度ls,和右端最长空区间的长度rs,即跨越左右子区间的最长空区间的长度为ls+rs;
例如:POJ 3667
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=5e4+10;
struct node{
int ls,rs,ms,pos,mark;
}tree[maxn*4];
/*线段树,其中节点i的懒惰标记:对应区间为tree[i].mark(0为未定,1为全空,2为全满)
*左端空区间的长度为tree[i].ls,右端区间的长度为tree[i].rs,最长子区间的长度为
*tree[i].ms,开始区间为tree[i].pos.
*/
int n,m; //房间数,请求数.
void build_tree(int l,int r,int i){
tree[i].ls=tree[i].rs=tree[i].ms=r-l+1;
tree[i].pos=l;
if(l==r) return;
int mid=l+r>>1;
build_tree(l,mid,i<<1);
build_tree(mid+1,r,i<<1|1);
}
bool all_space(int l,int r,int i){ //若结点i对应的区间[l,r]全空则返回1,否则返回0;
if(tree[i].ls==r-l+1) return 1;
return 0;
}
void update(int l,int r,int i){
if(!tree[i].mark) return; //若结点i的区间未定,则返回.
if(tree[i].mark==1){ //节点i全空,则r-l+1个空位置均分给左右子树,左右子数设全空状态.
int len=r-l+1;
tree[i<<1].ls=tree[i<<1].rs=tree[i<<1].ms=len+1>>1;
tree[i<<1].pos=l;
tree[i<<1|1].ls=tree[i<<1|1].rs=tree[i<<1|1].ms=len>>1;
tree[i<<1|1].pos=(l+r>>1)+1;
tree[i<<1].mark=tree[i<<1|1].mark=1;
}else{ //全满,0个空位置传给左右子树,左右子数设为全满状态.
tree[i<<1].ls=tree[i<<1].rs=tree[i<<1].ms=0;
tree[i<<1].pos=l;
tree[i<<1|1].ls=tree[i<<1|1].rs=tree[i<<1|1].ms=0;
tree[i<<1|1].pos=(l+r>>1)+1;
tree[i<<1].mark=tree[i<<1|1].mark=2;
}
tree[i].mark=0; //设节点i的状态待定.
}
int query(int d,int l,int r,int i){ //若线段树(根为i,对应区间为[l,r])存在长度为d的空区间,则返回其左指针,否则返回0.
update(l,r,i); //通过标记法维护线段树.
if(tree[i].ms<d) return 0;
if(tree[i].ms==d) return tree[i].pos;
int mid=l+r>>1;
if(tree[i<<1].ms>=d) //若左子树的空位不小于d,则递归左子树.
return query(d,l,mid,i<<1);
if(tree[i<<1].rs+tree[i<<1|1].ls>=d) //若跨越中间点的空子区间的不小于d,则返回空子区间的左指针,否则递归右子树.
return mid-tree[i<<1].rs+1;
return query(d,mid+1,r,i<<1|1);
}
void change(int tl,int tr,int l,int r,int i,bool flag){
//线段树(根为i,对应区间为[l,r])中插入或删除线段[tl,tr],插删标志为flag
if(tl>r||tr<l) return;
if(tl<=l&&r<=tr){ //线段[tl,tr]完全覆盖区间[l,r].
if(flag){ //若为插入操作,则节点i代表的区间全满.
tree[i].ls=tree[i].rs=tree[i].ms=0;
tree[i].pos=l;
tree[i].mark=2;
}else{
tree[i].ls=tree[i].rs=tree[i].ms=r-l+1;
tree[i].pos=l;
tree[i].mark=1;
}
return;
}
update(l,r,i);
int mid=l+r>>1;
change(tl,tr,l,mid,i<<1,flag);
change(tl,tr,mid+1,r,i<<1|1,flag);
//回溯往上进行修改.
tree[i].ls=tree[i<<1].ls;
if(all_space(l,mid,i<<1)) //如果左子树全为空
tree[i].ls+=tree[i<<1|1].ls;
tree[i].rs=tree[i<<1|1].rs;
if(all_space(mid+1,r,i<<1|1)) //如果右子树全为空
tree[i].rs+=tree[i<<1].rs;
//节点i所代表的区间的最长空区间的长度.
tree[i].ms=max(tree[i<<1].rs+tree[i<<1|1].ls,max(tree[i<<1].ms,tree[i<<1|1].ms));
if(tree[i].ms==tree[i<<1].ms) //最长空子区间在左子树.
tree[i].pos=tree[i<<1].pos;
else if(tree[i].ms==tree[i<<1].rs+tree[i<<1|1].ls)
tree[i].pos=mid-tree[i<<1].rs+1;
else tree[i].pos=tree[i<<1|1].pos;
}
int main()
{
scanf("%d%d",&n,&m);
memset(tree,0,sizeof tree);
build_tree(1,n,1);
while(m--){
int kind;
scanf("%d",&kind);
if(kind==1){ //入住请求
int d;
scanf("%d",&d); //输入入住的房间数
int ans=query(d,1,n,1); //检查线段树(根为i,对应区间为[l,r])是否存在长度为d的空区间,存在返回其左指针,否则返回0.
printf("%d\n",ans);
if(ans){
change(ans,ans+d-1,1,n,1,1);
}
}else{
int x,d;
scanf("%d%d",&x,&d); //x开始的d间房间退房.
change(x,x+d-1,1,n,1,0);
}
}
return 0;
}