线段树(四类)

文章目录

  • 线段树
  • 模板题
  • [P4513 小白逛公园](https://www.luogu.com.cn/problem/P4513)
  • [P3372 【模板】线段树 1](https://www.luogu.com.cn/problem/P3372)
  • [P3373 【模板】线段树 2](https://www.luogu.com.cn/problem/P3373)

线段树

复杂度O(m log n)、以下四个题目难度依次递增。

模板题

Code1:单点修改,查询区间和 (基础入门版)

#include
#include
#include
#include
using namespace std;
const int  N = 1e5+7;

int w[N];
struct node{	//树节点结构,节点编号为下标,u<<1 为左子树,u<<1|1为右子树 (与堆的存储一样)
	int l, r;
	int sum;
}tr[4*N];  // 叶子节点(N)+ 其他父节点(3N)

void pushup(int u)		//更新节点信息,这里是求和
{
	tr[u].sum = tr[u<<1].sum +tr[u<<1|1].sum; 
}

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);	//叶子节点变化,更新父节点信息
	}
}

int query(int u,int l,int r)	//查询函数,这里是求区间和
{
	if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum ;  //目标区间的子区间
	int mid = tr[u].l + tr[u].r >> 1;
	int ans =0 ;
	if(l <= mid) ans+=query(u<<1, l,r);		//递归左右子树查询
	if(r>=mid+1) ans += query(u<<1|1,l,r);
	return ans;
} 
void modify(int u,int x,int v)	//节点信息修改
{
	if(tr[u].l == tr[u].r ) tr[u].sum += v;  //修改叶子节点信息
	else {
		int mid = tr[u].l + tr[u].r >>1;
		if(x <= mid) modify(u<<1,x,v);
		else modify(u<<1|1,x,v);
		pushup(u);	//由于叶子节点信息变化,更新父节点
	}
}

int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) scanf("%d",&w[i]); //读入叶子节点权值
	build(1,1,n); //建树
	while(m--){
		int k,a,b;
		scanf("%d%d%d",&k,&a,&b);
		if(k==0) printf("%d\n",query(1,a , b)); //查询区间和
		else modify(1, a, b); //修改指定叶子节点权值;
	}
	return 0;
}

P4513 小白逛公园

Code2:单点修改,查询区间内最大连续子段和 (维护多个信息)

#include
#include

using namespace std;
const int N = 5e5+7;

int w[N];
struct Node{
	int l,r;
	int tmax,lmax,rmax,sum;
}tr[N*4];

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(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],w[l],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 x,int v)
{
	if(tr[u].l==x &&tr[u].r ==x) tr[u] = {x,x,v,v,v,v};
	else {
		int mid = tr[u].l+tr[u].r>>1;
		if(x<=mid) modify(u<<1,x,v);
		else 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 ans,left,right;
		left = query(u<<1,l,r);
		right = query(u<<1|1,l,r);
		pushup(ans,left,right);
		return ans;
	}
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	build(1,1,n);
	int k,x,y;
	while(m--){
		scanf("%d%d%d",&k,&x,&y);
		if(k==1) {
			if(x>y) swap(x,y);
			printf("%d\n",query(1,x,y).tmax); 
		}else modify(1,x,y);
	} 
	return 0;
}

P3372 【模板】线段树 1

Code3:区间加,查询区间和 (单个懒标记)

#include
#include

using namespace std;
const int N = 1e5+7;
typedef long long LL;

int w[N];
struct Node{
	int l,r;
	LL sum,add;
}tr[N*4];

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; //去除父节点懒标记 
	}
}

void build(int u,int l,int r)
{
	if(l==r) tr[u] = {l,r,w[l],0};
	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 v)
{
	if(tr[u].l>=l && tr[u].r <= r) {	//此节点代表的区间在目标区间里 
		tr[u].sum += (LL)(tr[u].r-tr[u].l+1)*v;
		tr[u].add += v;  //懒标记 
	}else{
		pushdown(u);	//每次分裂前要pushdown一下 
		int mid = tr[u].l +tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,v);  //左边交集 
		if(r>mid) modify(u<<1|1,l,r,v); //右边交集 
		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);  //分裂前 
	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()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	build(1,1,n);
	while(m--){
		int x,l,r,d;
		scanf("%d%d%d",&x,&l,&r);
		if(x==2) printf("%lld\n",query(1,l,r));
		else scanf("%d",&d) , modify(1,l,r,d);
	}
	return 0;
 } 

P3373 【模板】线段树 2

Code4:区间乘、区间加,查询区间和(双重懒标记)

#include

using namespace std;
typedef long long LL;
const int N = 1e5+7;

int n,m,p;
int w[N];
struct Node{
	int l,r;
	int sum,add,mul;
}tr[N*4];

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; 
	t.mul = (LL)t.mul*mul%p;	 //采取先乘后加,方便把懒标记pushdown给儿子 
	t.add = ((LL)t.add*mul+add)%p; // 公式:(sum*a+b)*c+d == sum*ac+bc+d 
}									//其中 sum*a+b为儿子的标记 
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 build(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;
		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) eval(tr[u],add,mul); //目标区间的子集,更新懒标记 
	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;
	pushdown(u);
	int mid = tr[u].l + tr[u].r>>1;
	int ans=0;
	if(l<=mid) ans =(ans + query(u<<1,l,r))%p;
	if(r>mid) ans =(ans + query(u<<1|1,l,r))%p;
	return ans;
}

int main()
{
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	build(1,1,n);
	int x,l,r,d;
	while(m--){
		scanf("%d%d%d",&x,&l,&r);
		if(x==3) printf("%d\n",query(1,l,r));
		else {
			scanf("%d",&d);
			if(x==1) modify(1,l,r,0,d);
			else modify(1,l,r,d,1);
		}
	}
	return 0;
 } 

你可能感兴趣的:(线段树&树状数组)