1.懒标记(pushup pushdown) 2.扫描线法
懒标记的含义:所有以当前区间为根节点的子树 要给整个子树都加上这个数 那么就在根节点添加
懒标记 然后给子树的每个点都加上懒标记的数(不包含根节点)
线段树的所有操作
pushup:子节点信息更新父节点信息
pushdown:父节点信息更新子节点信息
built:用堆的方式建立线段树
modify:修改一个值/一个区间的信息
query:询问一个区间的信息
pushup放在更新之后,pushdown放在更新之前
1.一个简单的整数问题2
细节:modify的pushup之前一定要pushdown一遍才能保证puahup更新的正确性
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int w[N];
int n,m;
struct Node
{
int l,r;
ll sum,add;
}tr[4*N];
void pushup(int u)//用子节点信息更新父节点信息
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)//用父节点信息更新子节点信息
{
auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.add)//假如有的加
{
left.add+=root.add,left.sum+=(ll)(left.r-left.l+1)*root.add;//左节点加上
right.add+=root.add,right.sum+=(ll)(right.r-right.l+1)*root.add;//右节点加上
root.add=0;//根节点重新赋值0
}
}
void built(int u,int l,int r)//建立线段树
{
if(l==r) tr[u]={l,r,w[l],0};//这个点的和等于w[l],add为0
else
{
tr[u]={l,r,0,0};
int mid=l+r>>1;
built(u<<1,l,mid),built(u<<1|1,mid+1,r);//继续建左右子树
pushup(u);//更新一下父节点信息
}
}
void modify(int u,int l,int r,int d)//修改操作
{
if(l<=tr[u].l&&r>=tr[u].r)//假如这个u区间已经在l~r里了,则直接修改
{
tr[u].sum+=(ll)(tr[u].r-tr[u].l+1)*d;//区间每个数都加上d
tr[u].add+=d;//这个add+=d
}
else
{
pushdown(u);//先把父节点的信息更新到子节点中去,在修改
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,d);//假如这个值在左区间,则递归左区间进行查找这个值进行修改
if(r>mid) modify(u<<1|1,l,r,d);//假如这个值在右区间,则递归右区间进行查找这个值进行修改
pushup(u);//修改完后更新父节点信息
}
}
ll query(int u,int l,int r)//询问操作
{
if(tr[u].l>=l&&r>=tr[u].r) return tr[u].sum;//假如这个区间在l~r中了,则直接返回
pushdown(u);//先把父节点的信息更新给子节点里
ll ans=0;
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) ans=query(u<<1,l,r);//假如在左区间
if(r>mid) ans+=query(u<<1|1,l,r);//假如在右区间,则加上
return ans;//返回结果
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
built(1,1,n);//建立线段树
char op[2];
while(m--)
{
int l,r,d;
scanf("%s%d%d",op,&l,&r);
if(*op=='Q') printf("%lld\n",query(1,l,r));//询问则直接输出
else
{
scanf("%d",&d);
modify(1,l,r,d);//修改
}
}
return 0;
}
2.亚特兰蒂斯
扫描线题目
然后用线段树进行两个操作
1.将某个区间 [ l,r ] + k (l,r指纵坐标)
2.整个区间长度大于0的区间总长为多少?也即len
线段树要存的节点信息
cnt:当前区间整个被覆盖的个数
len:不考虑祖宗节点cnt的前提下,cnt>0的区间总长度
性质:
1.永远只考虑根节点信息 ( 说明在query里面不会pushdown )
2.所有操作都是成对出现的 且先加后减 (modify也是不需要pushdown)
所以这题不需要pushdown
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int n;
struct Segment//用来存横坐标
{
double x,y1,y2;
int k;
bool operator< (const Segment &t)const//横坐标按照从小到大排序
{
return x<t.x;
}
}seg[N*2];
struct Node//存y,也即线段树
{
int l,r;
int cnt;//记录当前区间有多少个
double len;//当前区间y的长度
}tr[8*N];
vector<double> ys;//用来离散化,把y值离散化到1~2*n中
void pushup(int u)//用子节点信息更新父节点信息
{
if(tr[u].cnt) tr[u].len=ys[tr[u].r+1]-ys[tr[u].l];//假如当前区间被覆盖了,则加上对应的y的长度
else if(tr[u].l!=tr[u].r) tr[u].len=tr[u<<1].len+tr[u<<1|1].len;//假如没有覆盖并且不是根节点,则长度等于两个子节点的长度和
else tr[u].len=0;//假如是根节点则没有长度
}
void built(int u,int l,int r)//建立线段树
{
tr[u]={l,r,0,0};//刚开始没操作的长度与覆盖数都为0
if(l!=r)
{
int mid=l+r>>1;
built(u<<1,l,mid),built(u<<1|1,mid+1,r);//建立左右区间
//pushup(u)这里不用也行,因为刚开始没有数的时候就是0
}
}
void modify(int u,int l,int r,int k)//修改某个区间的值
{
if(tr[u].l>=l&&tr[u].r<=r)//假如当前区间在l~r中
{
tr[u].cnt+=k;//则直接修改
pushup(u);//把修改传到子节点中
}
else
{
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,k);//假如在左边有,则递归左边
if(r>mid) modify(u<<1|1,l,r,k);//假如在右边有,则递归右边
pushup(u);
}
}
int find(double y)//离散化,把y离散到坐标里
{
return lower_bound(ys.begin(),ys.end(),y)-ys.begin();//用二分找出y的位置
}
int main()
{
int T=1;
while(scanf("%d",&n),n)
{
ys.clear();//清空上一层的状态
double x1,x2,y1,y2;
for(int i=1,j=0;i<=n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
seg[j++]={x1,y1,y2,1},seg[j++]={x2,y1,y2,-1};//左边则+1,右边则-1,加到存x中的结构体中去
ys.push_back(y1),ys.push_back(y2);//把y也存进离散化里头
}
//将y去重
sort(ys.begin(),ys.end());
ys.erase(unique(ys.begin(),ys.end()),ys.end());
built(1,0,ys.size()-2);//建立一颗线段树
sort(seg,seg+2*n);//将x从小到大排序
double res=0;
for(int i=0;i<2*n;i++)
{
if(i) res+=tr[1].len*(seg[i].x-seg[i-1].x);//当i>0才能计算
modify(1,find(seg[i].y1),find(seg[i].y2)-1,seg[i].k);//修改线段树y中的值
}
printf("Test case #%d\n",T++);
printf("Total explored area: %.2lf\n\n",res);
}
return 0;
}
3.维护序列
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int w[N];
int n,m,p;
struct Node//线段树
{
int l,r;
int sum,add,mul;
}tr[4*N];
void pushup(int u)//子节点更新父节点
{
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
void eval(Node &t,int add,int mul)//用来更新某个节点的值
{
t.sum=((ll)t.sum*mul+(ll)(t.r-t.l+1)*add)%p;//总和等于原来的和*mul+数的多少*add
t.mul=(ll)t.mul*mul%p;//新的mul等于原来的mul*mul
t.add=((ll)t.add*mul+add)%p;//新的add=原来的add*mul+add
}
void pushdown(int u)//用父节点信息更新子节点信息
{
eval(tr[u<<1],tr[u].add,tr[u].mul);//更新左节点信息
eval(tr[u<<1|1],tr[u].add,tr[u].mul);//更新右节点信息
tr[u].add=0,tr[u].mul=1;//清空
}
void built(int u,int l,int r)//用来建立线段树
{
if(l==r) tr[u]={l,r,w[l],0,1};
else
{
tr[u]={l,r,0,0,1};
int mid=l+r>>1;
built(u<<1,l,mid),built(u<<1|1,mid+1,r);//建立左右子树
pushup(u);//更新一遍父节点信息
}
}
void modify(int u,int l,int r,int add,int mul)//修改操作
{
if(tr[u].l>=l&&tr[u].r<=r) eval(tr[u],add,mul);//假如这个区间在l~r之间直接更新
else
{
pushdown(u);//把之前的父节点信息更新到子节点信息中去
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,add,mul);//假如左边有这部分区间,则递归更新
if(r>mid) modify(u<<1|1,l,r,add,mul);//假如右边有这部分区间,则递归更新
pushup(u);//更新一遍父节点信息
}
}
int query(int u,int l,int r)//询问操作
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;//假如这个区间在l~r之间则直接返回sum
pushdown(u);//把父节点信息传递到子节点中去
int mid=tr[u].l+tr[u].r>>1;
int res=0;
if(l<=mid) res=query(u<<1,l,r);//假如左边有这个区间
if(r>mid) res=(res+query(u<<1|1,l,r))%p;//假如右边也有这个区间则加上
return res;//返回答案
}
int main()
{
scanf("%d%d",&n,&p);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
built(1,1,n);//建立线段树
scanf("%d",&m);
int op,l,r,d;
while(m--)
{
scanf("%d%d%d",&op,&l,&r);
if(op==1)
{
scanf("%d",&d);
modify(1,l,r,0,d);//乘d相当于加0乘d
}
else if(op==2)
{
scanf("%d",&d);
modify(1,l,r,d,1);//加d相当于加d乘1
}
else printf("%d\n",query(1,l,r));//输出询问
}
return 0;
}