线段树

线段树的五种操作

线段树的结构(数组开4倍)

 ① 建立线段树 build

② 查询操作 query

        判断查询区间[L,R]和线段树区间[TL,TR]的关系。如果线段树区间被查询区间包含,则直接返回需要的信息,如果交叉,则分情况对左右区间进行递归。

③ 修改操作 modify

        单点修改,递归到对应的叶节点修改,然后pushup即可

        区间修改,使用last标记

④ 更新操作

        pushup(u) 将子节点的信息更新到父节点

        pushdown(u) 搭配lazy标记,将子节点的更新记录在父节点,等查询时再递归将lazy标记更新到子节点,然后清空父节点的lazy标记


最大数(单点修改,区间查询)

1275. 最大数 - AcWing

维护区间最大值

struct node{
	int l,r;
	ll maxn;
}tr[N<<2];
ll m,p;
int n;
void pushup(int u){
	tr[u].maxn=max(tr[u<<1].maxn,tr[u<<1|1].maxn);
}
void build(int u,int l,int r){
	if(l==r){
		tr[u].l=l;
		tr[u].r=r;
	}else{
		tr[u].l=l;
		tr[u].r=r;
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);//只在非叶节点pushup 
	}
}
//单点修改 
void modify(int u,int x,ll c){
	if(tr[u].l==tr[u].r&&tr[u].l==x){
		tr[u].maxn=c;
	}else{
		int mid=tr[u].l+tr[u].r>>1;
		if(x<=mid) modify(u<<1,x,c);
		else modify(u<<1|1,x,c);
		pushup(u);//只在非叶节点pushup 
	}
}
//区间查询
ll query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r) return tr[u].maxn;
	int mid=tr[u].l+tr[u].r>>1;
	ll res=-1;
	if(l<=mid) res=max(res,query(u<<1,l,r));
	if(r>mid) res=max(res,query(u<<1|1,l,r));
	return res;
} 
int main(){
	cin>>m>>p;
	build(1,1,m);//最多有m个点 
	ll last=0;
	while(m--){
		char c;cin>>c;
		if(c=='Q'){
			int l;cin>>l;
			last=query(1,n-l+1,n);
			cout<<last<<endl;
		}else{
			ll t;cin>>t;
			t=(t+last)%p;
			modify(1,++n,t);
		}
	}
}

你能回答这些问题吗(单点修改,区间查询)

245. 你能回答这些问题吗 - AcWing题库

维护区间的最大左前缀和以及最大右前缀和

struct Node{
	int l,r;
	int sum;//区间总和 
	int lmax;//最大前缀和 
	int rmax;//最大后缀和 
	int tmax;//连续序列和 
}tree[N<<2];
void pushup(Node &u,Node &l,Node &r){
	u.sum=l.sum+r.sum;
	u.lmax=max(l.lmax,l.sum+r.lmax);
	u.rmax=max(r.rmax,r.sum+l.rmax);
	u.tmax=max(max(l.tmax,r.tmax),l.rmax+r.lmax);
} 
void pushup(int u){
	pushup(tree[u],tree[u<<1],tree[u<<1|1]);
}
void build(int u,int l,int r){
	if(l==r) tree[u]={l,r,a[l],a[l],a[l],a[l]};
	else
	{
		tree[u].l=l,tree[u].r=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 x,int y){
	if(tree[u].l==x&&tree[u].r==x) 
		tree[u]={x,x,y,y,y,y};
	else{
		int mid=tree[u].l+tree[u].r>>1;
		if(x<=mid) modify(u<<1,x,y);
		else modify(u<<1|1,x,y);
		pushup(u);
	}
}
Node query(int u,int l,int r){
	if(tree[u].l>=l&&tree[u].r<=r) return tree[u];
	else{
		int mid=tree[u].l+tree[u].r>>1;
		if(r<=mid) return query(u<<1,l,r);
		else if(l>mid) return query(u<<1|1,l,r);
		else{
			Node left=query(u<<1,l,r);
			Node right=query(u<<1|1,l,r);
			Node res;//利用pushup计算左右区间的连续区间和 
			pushup(res,left,right);
			return res;
		}
	} 
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	while(m--){
		int k,x,y;cin>>k>>x>>y;
		if(k&1){
			if(x>y) swap(x,y);
			cout<<query(1,x,y).tmax<<endl;
		}
		else{
			modify(1,x,y);
		}
	}
} 

区间最大公约数(区间修改,区间查询)

① 维护差分数组,将区间修改变为单点修改

② 维护区间的最大公约数,利用原数组的最大公约数就等于差分数组的最大公约数的性质

查询[l,r]的最大公约数时,gcd(a[l],a[l+1],...,a[r])=gcd(a[l],a[l+1]-a[l],...,a[r]-a[r-1])

所以我们先求出差分中b[l+1]~b[r]的最大公约数,即query(1,l+1,r),然后和a[l]再求一次最大公约数即为区间的最大公约数,由于a[l]可能被修改,所以使用query(1,1,l).sum求出当前的a[l],答案即为gcd(query(1,1,l).sum,query(1,l+1,r).d)

#include<iostream>
#include<algorithm>
using namespace std;
const int N=500010;
typedef long long ll;
struct Node{
	int l,r;
	ll sum;//区间和 
	ll d; //区间最大公约数 
}tr[4*N];
ll gcd(ll a,ll b){
	return b?gcd(b,a%b):a;
}
int n,m;
ll w[N];
void pushup(Node &u,Node &l,Node &r){
	u.sum=l.sum+r.sum;
	u.d=gcd(l.d,r.d);
}
void pushup(int u){
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
	if(l==r){
		tr[u]={l,r,w[l]-w[l-1],w[l]-w[l-1]};
		return;
	}
	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 x,ll v){
	if(tr[u].l==tr[u].r&&tr[u].l==x){
		tr[u].sum+=v;
		tr[u].d+=v;
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	if(x<=mid) modify(u<<1,x,v);
	if(x>mid) modify(u<<1|1,x,v);
	pushup(u);
}
Node query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r) return tr[u];
	int mid=tr[u].l+tr[u].r>>1;
	if(r<=mid) return query(u<<1,l,r);
	else if(l>mid) return query(u<<1|1,l,r);
	else{
		Node left=query(u<<1,l,r);
		Node right=query(u<<1|1,l,r);
		Node res;
		pushup(res,left,right);
		return res;
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		string op;cin>>op;
		if(op=="C"){
			int l,r;ll d;cin>>l>>r>>d;
			modify(1,l,d);
			if(r+1<=n)
			modify(1,r+1,-d);
		}
		else{
			int l,r;cin>>l>>r;
			if(l==r){//区间只有一个数 
				Node a=query(1,1,l);
				cout<<abs(a.sum)<<endl;
				continue;
			}
			Node a=query(1,1,l),b=query(1,l+1,r);
			ll res=abs(gcd(a.sum,b.d));
			cout<<res<<endl;
		}
	}
} 

一个简单的整数问题2(区间修改,区间查询)

243. 一个简单的整数问题2 - AcWing题库

        维护区间和,用lazy标记记录更新信息

注意使用lazy标记时,pushdown完要清空父节点lazy标记,pushdown需要在modify中分区间修改以及query分区间查询时使用

struct node{
	int l,r;
	ll sum;
	int lazy;
}tr[N<<2];
void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
//父节点lazy标记传递,更新子节点信息 
void pushdown(int u){
	node &root=tr[u];
	node &left=tr[u<<1];
	node &right=tr[u<<1|1];
	left.sum+=(ll)(left.r-left.l+1)*root.lazy;
	left.lazy+=root.lazy;
	right.sum+=(ll)(right.r-right.l+1)*root.lazy;
	right.lazy+=root.lazy;
	root.lazy=0;//清空父节点lazy
}
void build(int u,int l,int r){
	if(l==r){
		tr[u]={l,r,w[l]};
	}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,int d){
	if(tr[u].l>=l&&tr[u].r<=r){
		tr[u].sum+=(ll)(tr[u].r-tr[u].l+1)*d;
		tr[u].lazy+=d;
		return;
	}
	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&&tr[u].r<=r) return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	ll res=0;
	if(l<=mid) res+=query(u<<1,l,r);
	if(r>mid) res+=query(u<<1|1,l,r);
	return res;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		string op;cin>>op;
		if(op=="Q"){
			ll l,r;cin>>l>>r;
			cout<<query(1,l,r)<<endl; 
		}
		else{
			ll l,r,d;cin>>l>>r>>d;
			modify(1,l,r,d);
		}
	}
}

维护序列(区间修改,区间查询)

 1277. 维护序列 - AcWing

维护区间和,但要分别利用两个懒标记来处理区间加和区间乘的更新信息

struct node{
	int l,r;
	ll sum;//区间总和 
	ll add;//加的懒标记 
	ll mul;//乘的懒标记 
}tr[N<<2];
void pushup(int u){
	tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
//传递lazy标记,更新子节点 
void pushdown(int u){
	node &root=tr[u];
	node &left=tr[u<<1];
	node &right=tr[u<<1|1];
	left.sum=(ll)left.sum*root.mul%p;
	left.sum=(left.sum+(left.r-left.l+1)*root.add)%p;
	left.add=(left.add*root.mul+root.add)%p;
	left.mul=(left.mul*root.mul)%p;
	right.sum=(ll)right.sum*root.mul%p;
	right.sum=(right.sum+(right.r-right.l+1)*root.add)%p;
	right.add=(right.add*root.mul+root.add)%p;
	right.mul=(right.mul*root.mul)%p;
	root.add=0;
	root.mul=1;
}
void build(int u,int l,int r){
	if(l==r) tr[u]={l,r,a[l],0,1};
	else{
		tr[u]={l,r,0,0,1};
		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,int add,int mul){
	if(tr[u].l>=l&&tr[u].r<=r){
		tr[u].sum=(ll)tr[u].sum*mul%p;
		tr[u].sum=(tr[u].sum+(tr[u].r-tr[u].l+1)*add)%p;
		tr[u].add=(tr[u].add*mul+add)%p;
		tr[u].mul=(tr[u].mul*mul)%p;
		return;
	}
	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);
}
//区间查询 
ll query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	ll res=0; 
	if(l<=mid) res+=query(u<<1,l,r);
	if(r>mid) res+=query(u<<1|1,l,r);
	return res%p;
}
int main(){
	cin>>n>>p;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	int m;cin>>m;
	while(m--){
		int t;cin>>t;
		if(t==1){
			int a,b,c;cin>>a>>b>>c;
			modify(1,a,b,0,c);
		}
		else if(t==2){
			int a,b,c;cin>>a>>b>>c;
			modify(1,a,b,c,1);
		}
		else{
			int a,b;cin>>a>>b;
			cout<<query(1,a,b)<<endl;
		}
	}
}

 油漆面积(扫描线)

 1228. 油漆面积 - AcWing

         对于每个矩形的左右边,左边入边,右边为出边,对于矩形(x1,y1,x2,y2),两条边表示为(x1,y1,y2,1)(x2,y1,y2,-1)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e4+5;
struct segment{
	int x,y1,y2;
	int k;//k=1/2 左/右侧边 
	bool operator<(const segment &t)const{
		return x<t.x;
	}
}seg[N*2];//n个矩形,2*n个线段
struct node{
	int l,r;
	int cnt;//区间被覆盖次数 
	int len;//被覆盖区间的长度 
}tr[N*4];
void pushup(int u){
	if(tr[u].cnt>0){
		tr[u].len=tr[u].r-tr[u].l+1;
	}else if(tr[u].l==tr[u].r){
		tr[u].len=0;
	}else{
		tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
	}
}
void build(int u,int l,int r){
	if(l==r) tr[u]={l,r,0,0};
	else{
		tr[u]={l,r};
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		tr[u].cnt=0;
		tr[u].len=0;
	}
}
void modify(int u,int l,int r,int k){
	if(tr[u].l>=l&&tr[u].r<=r){
		tr[u].cnt+=k;
		pushup(u);
		return;
	}
	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 main(){
	int n;cin>>n;
	int m=0;
	for(int i=1;i<=n;i++){
		int x1,x2,y1,y2;
		cin>>x1>>y1>>x2>>y2;
		seg[m++]={x1,y1,y2,1};
		seg[m++]={x2,y1,y2,-1};
	}
	sort(seg,seg+m); 
	build(1,0,10000);//0<=x<=10000 
	int res=0;
	//逐个矩形边处理 
	for(int i=0;i<m;i++){
		//根节点的len 表示两个相邻竖边内被阴影部分覆盖的总高度 
		if(i>0) res+=tr[1].len*(seg[i].x-seg[i-1].x);
		modify(1,seg[i].y1,seg[i].y2-1,seg[i].k);
	}
	cout<<res;
}

亚特兰蒂斯(扫描线)

247. 亚特兰蒂斯 - AcWing题库

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long ll;
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{
	int l,r;
	int cnt;
	double len;
}tr[N*8];//4*2N
vector<double> ys;//y轴坐标离散化 
//返回vector 中第一个 >= y 的数的下标
int find(double y){
	return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
}
void pushup(int u){
	if(tr[u].cnt){
		tr[u].len=ys[tr[u].r+1]-ys[tr[u].l];
	}else if(tr[u].l==tr[u].r){
		tr[u].len=0;
	}else{
		tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
	}
}
void build(int u,int l,int r){
	if(l==r) tr[u]={l,r,0,0};
	else{
		tr[u]={l,r};
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		tr[u].cnt=0;
		tr[u].len=0;
	}
}
void modify(int u,int l,int r,int k){
	if(tr[u].l>=l&&tr[u].r<=r){
		tr[u].cnt+=k;
		pushup(u);
		return;
	}
	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 main(){
	int t=1; 
	while(cin>>n&&n){
		ys.clear();
		for(int i=0,j=0;i<n;i++){
			double x1,x2,y1,y2;
			cin>>x1>>y1>>x2>>y2;
			seg[j++]={x1,y1,y2,1};
			seg[j++]={x2,y1,y2,-1};
			ys.push_back(y1);
			ys.push_back(y2); 
		}
		sort(ys.begin(),ys.end());
		ys.erase(unique(ys.begin(),ys.end()),ys.end());
		build(1,0,ys.size()-2);
		sort(seg,seg+2*n);
		double res=0;
		//根节点的长度即为此时有效线段长度
		//乘此线段x轴和下一线段x轴的坐标为面积 
		for(int i=0;i<2*n;i++){
			if(i>0) res+=tr[1].len*(seg[i].x-seg[i-1].x);
			modify(1,find(seg[i].y1),find(seg[i].y2)-1,seg[i].k);
		}
		printf("Test case #%d\n", t++);
        printf("Total explored area: %.2lf\n\n", res);	
	}
}

环形数组(区间修改,区间查询)

3805. 环形数组 - AcWing题库

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=2e5+5;
int n,m;
int a[N];
struct node{
	int l,r;
	ll lazy;
	ll w;
}tr[N<<2];
void pushup(int u){
	tr[u].w=min(tr[u<<1].w,tr[u<<1|1].w);
}
void pushdown(int u){
	tr[u<<1].lazy+=tr[u].lazy;
	tr[u<<1].w+=tr[u].lazy;
	tr[u<<1|1].lazy+=tr[u].lazy;
	tr[u<<1|1].w+=tr[u].lazy;
	tr[u].lazy=0;
}
void build(int u,int l,int r){
	if(l==r) tr[u]={l,r,0,a[l]};
	else{
		tr[u]={l,r,0};
		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,int d){
	if(tr[u].l>=l&&tr[u].r<=r){
		tr[u].w+=d;
		tr[u].lazy+=d;
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	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&&tr[u].r<=r) return tr[u].w;
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	ll res=1e18;
	if(l<=mid) res=min(res,query(u<<1,l,r));
	if(r>mid) res=min(res,query(u<<1|1,l,r));
	return res;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	cin>>m;
	getchar();
	while(m--){
		stringstream ss;string s;
		getline(cin,s);
		ss<<s;
		int t,c=0,op[5];
		while(ss>>t){
			op[++c]=t;
		}
		int l=op[1]+1,r=op[2]+1,d=op[3];
		if(c==2){
			if(l<=r){
				cout<<query(1,l,r)<<endl;
			}else{
				cout<<min(query(1,l,n),query(1,1,r))<<endl;
			}
		}else{
			if(l<=r){
				modify(1,l,r,d);
			}else{
				modify(1,l,n,d);
				modify(1,1,r,d);
			}
		}
	}
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vic.GoodLuck

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值