线段树详解(原理,实现,应用)入门篇

线段树详解(原理,实现,应用)入门篇

原理:

请见这篇blog。

https://www.cnblogs.com/AC-King/p/7789013.html

实现:

建树:

堆式建树

const int N=1e5+10;
int a[N],n,m;
struct SegmentTree{
	int l,r;
	long long maxn,add;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define Max(x) tree[x].maxn
}tree[N<<2];// N<<2=N*4, 注意一定要开四倍空间

void push_up(int p){// 从下向上更新信息
	Max(p)=max(Max(p*2),Max(p*2+1));
}

void build(int p,int l,int r){
	l(p)=l, r(p)=r;// 节点p代表区间[l,r]
	if(l==r){
		Max(p)=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);// 左子节点[l,mid], 编号p*2
	build(p*2+1,mid+1,r);// 右子节点[mid+1,r], 编号p*2+1
	push_up(p);
}

build(1,1,n);// 使用

单点修改

// 将a[x]修改为v,时间复杂度为O(logN)
void change_max(int p,int x,int v){
	if(l(p)==r(p)){ Max(p)=v; return;}// 找到叶子节点
	int mid=(l(p)+r(p))/2;
	if(x<=mid) change_max(p*2,x,v);// x属于左区间
	if(x>mid) change_max(p*2+1,x,v);// x属于右区间
	push_up(p);
}

change_max(1,x,v);// 使用

区间查询

long long ask_max(int p,int l,int r){
	if(l<=l(p)&&r(p)<=r) return Max(p);//完全包含
	int mid=(l(p)+r(p))/2;
	long long res=0;
	if(l<=mid) res=max(res,ask_max(p*2,l,r));// 与左子节点有重叠
	if(r>mid) res=max(res,ask_max(p*2+1,l,r));// 与右子节点有重叠
	return res;
}

cout<<ask_max(1,l,r)<<'\n';// 使用

单点修改+区间查询最大值Code

#include<bits/stdc++.h>
#define ri register int 
#define rll  register long long
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10;
int n,m;
int a[N];

template<typename T>inline void read(T &x){// 快读 
	char ch=getchar();int fx=0;x=0;
	for(;!isdigit(ch);ch=getchar()) fx|=(ch=='-');
	for(;isdigit(ch);ch=getchar()) x=((x<<3)+(x<<1)+(ch^48));
	x=(fx?-x:x);
}
template<typename T>inline void write(T x){// 快写 
	if(x<0) putchar('-'), x=-x;
	if(x>9) write(x/10);
	putchar(x%10^'0');
}

struct SG{
	int l,r;
	long long maxn;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define Max(x) tree[x].maxn
}tree[N<<2];

void push_up(int p){// 从下向上更新信息
	Max(p)=max(Max(p*2),Max(p*2+1));
}

void build(int p,int l,int r){
	l(p)=l, r(p)=r;// 节点p代表区间[l,r]
	if(l==r){
		Max(p)=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);// 左子节点[l,mid], 编号p*2
	build(p*2+1,mid+1,r);// 右子节点[mid+1,r], 编号p*2+1
	push_up(p);
}

// 将a[x]修改为v,时间复杂度为O(logN)
void change_max(int p,int x,int v){
	if(l(p)==r(p)){ Max(p)=v; return;}// 找到叶子节点
	int mid=(l(p)+r(p))/2;
	if(x<=mid) change_max(p*2,x,v);// x属于左区间
	if(x>mid) change_max(p*2+1,x,v);// x属于右区间
	push_up(p);
}

long long ask_max(int p,int l,int r){
	if(l<=l(p)&&r(p)<=r) return Max(p);//完全包含
	int mid=(l(p)+r(p))/2;
	long long res=0;
	if(l<=mid) res=max(res,ask_max(p*2,l,r));// 与左子节点有重叠
	if(r>mid) res=max(res,ask_max(p*2+1,l,r));// 与右子节点有重叠
	return res;
}

int main(){
	read(n), read(m);
	for(ri i=1;i<=n;i++) read(a[i]);
	build(1,1,n);
	while(m--){
		int op,l,r,k; 
		read(op);
		switch(op){
			case 1: int x,v; read(x), read(v); change_max(1,x,v); break;// 单点修改 
			case 2: read(l), read(r); write(ask_max(1,l,r)), cout<<'\n'; break;// 区间查询最大值
		}
	}
	return 0;
}

区间修改–延迟标记

如果暴力处理区间修改,那么时间复杂度就变成了O(N), 再加上有m次操作,时间复杂度就来到了O(N*M),自然是不优秀的,会被大数据卡掉。这时,引入延迟标记,就使时间复杂度减少到了O( l o g N {log_N} logN) 。

来一个区间加的模板题。

题目:P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

区间加线段树Code

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10;
int n,m;
int a[N];

template<typename T>inline void read(T &x){// 快读 
	char ch=getchar();int fx=0;x=0;
	for(;!isdigit(ch);ch=getchar()) fx|=(ch=='-');
	for(;isdigit(ch);ch=getchar()) x=((x<<3)+(x<<1)+(ch^48));
	x=(fx?-x:x);
}
template<typename T>inline void write(T x){// 快写 
	if(x<0) putchar('-'), x=-x;
	if(x>9) write(x/10);
	putchar(x%10^'0');
}

struct SegmentTree{
	int l,r;
	long long sum,add;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define sum(x) tree[x].sum
	#define add(x) tree[x].add
}tree[N<<2];// 线段树 

void push_up(int p){// 从下向上更新信息
	sum(p)=sum(p*2)+sum(p*2+1);
}

void push_down(int p){// 从上向下更新信息
	if(add(p)){// 节点p有标记 
		sum(p*2)+=add(p)*(r(p*2)-l(p*2)+1);// 更新左子节点信息 
		sum(p*2+1)+=add(p)*(r(p*2+1)-l(p*2+1)+1);// 更新右子节点信息
		add(p*2)+=add(p);// 左子节点打延迟标记 
		add(p*2+1)+=add(p);// 右子节点打延迟标记 
		add(p)=0;
	}
}

void build(int p,int l,int r){// 建树 
	l(p)=l, r(p)=r;
	if(l==r){ sum(p)=a[l]; return;}
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	push_up(p);
}

void change(int p,int l,int r,int d){// 将[l,r]加d,时间复杂度为O(logN)
	if(l<=l(p)&&r>=r(p)){// 完全覆盖 
		sum(p)+=(long long)d*(r(p)-l(p)+1);
		add(p)+=d;// 给节点打延迟标记 
		return;
	}
	push_down(p);// 下传延迟标记 
	int mid=(l(p)+r(p))/2;
	if(l<=mid) change(p*2,l,r,d);
	if(r>mid) change(p*2+1,l,r,d);
	push_up(p);
}

long long ask(int p,int l,int r){// 询问[l,r]的和 
	if(l<=l(p)&&r>=r(p)) return sum(p);// 完全覆盖 
	push_down(p);// 下传延迟标记 
	int mid=(l(p)+r(p))/2;
	long long val=0;
	if(l<=mid) val+=ask(p*2,l,r);
	if(r>mid) val+=ask(p*2+1,l,r);
	return val;
}

int main(){
	read(n), read(m);
	for(int i=1;i<=n;i++) read(a[i]);
	build(1,1,n);
	while(m--){
		int num,l,r,k;
		read(num), read(l), read(r);
		if(num==1) read(k), change(1,l,r,k);
		if(num==2) cout<<ask(1,l,r)<<'\n';
	}
	return 0;
}

再来一个较难的区间加和区间乘的线段树。

题目:P3373 【模板】线段树 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这个题就需要两个lazy标记——add,mul, 记住一定要在mul标记时之前的add标记要全部下传,不然就会出问题。这里有一篇blog讲解了为什么,不懂的朋友可以看一看。

[题解 P3373 【【模板】线段树 2】 - lqhsr 的博客 - 洛谷博客 (luogu.com.cn)]

加乘线段树Code

提示: n < < 1 = n × 2 , n < < 1 ∣ 1 = n × 2 + 1 n<<1=n {\times}2, n<<1|1=n{\times}2+1 n<<1=n×2n<<1∣1=n×2+1

#include<bits/stdc++.h>
#define ri register int
#define rll register long long
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int N=1e5+10;
int n,T,mod;
int a[N];
struct SG{
    int l,r;
    ll sum,add,mul;
    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define sum(x) tree[x].sum
    #define add(x) tree[x].add
    #define mul(x) tree[x].mul
}tree[N<<2];

template<typename T>inline T read(T &x){
    char ch=getchar(); int fx=0; x=0;
    for( ;!isdigit(ch);ch=getchar()) fx|=(ch=='-');
    for( ;isdigit(ch);ch=getchar()) x=x*10+(ch-'0');
    return (fx? -x: x);
}
template<typename T>inline void write(T x){
    if(x<0) putchar('-'), x=-x;
    if(x>9) write(x/10);
    putchar(x%10^48);
}

void push_up(int p){
    sum(p)=(sum(p<<1)+sum(p<<1|1))%mod;
}

void push_down(int p){
    sum(p<<1)=(sum(p<<1)*mul(p)+add(p)*(r(p<<1)-l(p<<1)+1)%mod)%mod;
    sum(p<<1|1)=(sum(p<<1|1)*mul(p)+add(p)*(r(p<<1|1)-l(p<<1|1)+1)%mod)%mod;// add已经乘过mul了 
    mul(p<<1)=mul(p<<1)*mul(p)%mod;
    mul(p<<1|1)=mul(p<<1|1)*mul(p)%mod;
    add(p<<1)=(add(p<<1)*mul(p)+add(p))%mod;
    add(p<<1|1)=(add(p<<1|1)*mul(p)+add(p))%mod;
    add(p)=0, mul(p)=1;
}

void build(int p, int l, int r){
    l(p)=l, r(p)=r, mul(p)=1;
    if(l==r){ sum(p)=a[l]%mod; return;}
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    push_up(p);
}

void change_add(int p, int l, int r,int d){// 区间加
    if(l<=l(p)&&r(p)<=r){
        sum(p)=(sum(p)+1ll*d*(r(p)-l(p)+1))%mod;
        add(p)=(add(p)+d)%mod;
        return;
    }
    push_down(p);
    int mid=(l(p)+r(p))>>1;
    if(l<=mid) change_add(p<<1,l,r,d);
    if(r>mid) change_add(p<<1|1,l,r,d);
    push_up(p);
}

void change_mul(int p, int l, int r,int d){// 区间乘
    if(l<=l(p)&&r(p)<=r){
        sum(p)=sum(p)*d%mod;
        add(p)=(add(p)*d)%mod;
        // 注意:add要在这里乘上d,因为后面可能要加其他的数,而那些数其实是不用乘d的 
        mul(p)=mul(p)*d%mod;
        return;
    }
    push_down(p);
    int mid=(l(p)+r(p))>>1;
    if(l<=mid) change_mul(p<<1,l,r,d);
    if(r>mid) change_mul(p<<1|1,l,r,d);
    push_up(p);
}

ll ask_sum(int p, int l, int r){// 区间求和
    if(l<=l(p)&&r(p)<=r) return sum(p);
    push_down(p);
    int mid=(l(p)+r(p))>>1;
    ll res=0;
    if(l<=mid) res=(res+ask_sum(p<<1,l,r))%mod;
    if(r>mid) res=(res+ask_sum(p<<1|1,l,r))%mod;
    return res;
}

int main(){
    read(n),read(T), read(mod);
    for(ri i=1;i<=n;i++) read(a[i]);
    build(1,1,n); 
    while(T--){
        int op,l,r,k;
        read(op);
        switch(op){
            case 1: {read(l), read(r), read(k), change_mul(1,l,r,k); break;}
            case 2: {read(l), read(r), read(k), change_add(1,l,r,k); break;}
            case 3: {read(l), read(r), write(ask_sum(1,l,r)), cout<<'\n'; break;}
        }
    }
    return 0;
}

这里有一篇比较ok的blog,链接如下。

题解 P3373 【模板】线段树 2 - 小朋友的博客 - 洛谷博客 (luogu.com.cn)

这篇线段树入门篇中的基本操作你学会了吗?若还不太熟练,那我建议多去洛谷上刷一些有关线段树的题。麻烦点赞,收藏qwq!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值