线段树总结

1、acwing 245 线段树 + 思维

题面:给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
  1. 1 x y,查询区间 x, y 中的最大连续子段和,即 m a x x ≤ l ≤ r ≤ y ∑ i = l r A [ i ] max_{x≤l≤r≤y} {\sum_{i=l}^{r}A[i]} maxxlryi=lrA[i]
  2. 2 x y,把 A[x]的值改成 y。
解法
  • 两个操作分别为:区间查询、单点修改 ---- 线段树

  • 最大连续字段和:2 ,-1,3 ,sum=3,思考方法:

  • struct node{

    ​ int L,R; 区间左右端点

    ​ int tmax; 最大连续子段和

    ​ int lmax; 最大前缀和

    ​ int rmax; 最大后缀和

    ​ int sum; 区间和

    }

  • 最大连续子段和的求法:由于已经通过线段树分了区间
  • 最大子段和 = max(左儿子,右儿子,中间部分)​
    • 如 8 -1 3 7 -2 9 中间部分为最大连续子段:3+7=10
    • 因此 t m a x = m a x ( m a x ( 左 儿 子 t m a x , 右 t m a x ) , 左 儿 子 后 缀 + 右 儿 子 前 缀 ) tmax = max(max(左儿子tmax, 右tmax) , 左儿子后缀+右儿子前缀) tmax=max(max(tmax,tmax),+)
    • 前后缀的求法: 以前缀为例,分类讨论
      • 左儿子的前缀
      • 左儿子的sum + 右儿子的前缀

在这里插入图片描述

线段树代码主要注意点:

1、pushup操作:目的是将更新后的子节点值传回父节点,更新父节点
2、build操作的 递归终止条件 + 初始化
3、mid取的都是树根的中点: ( t r e e [ u ] . l + t r e e [ u ] . r ) / 2 (tree[u].l + tree[u].r) / 2 (tree[u].l+tree[u].r)/2
4、modify修改操作中只用针对递归返回的加 pushup
5、query查询操作:分类讨论,注意 l <= mid 与 r > mid

AC代码:

#include <bits/stdc++.h>
#define INF 0x3f3f3f
using namespace std;

const int N = 510000;

int n,m;
int a[N];
struct node{
	int l,r;
	int lmax, rmax, tmax , sum;
}tree[N*4];

void pushup(node &u, node &s1, node &s2){
	u.tmax = max(max(s1.tmax , s2.tmax) , s1.rmax + s2.lmax);
	u.lmax = max(s1.lmax , s1.sum + s2.lmax);
	u.rmax = max(s2.rmax , s2.sum + s1.rmax);
	u.sum = s1.sum + s2.sum;
}

void pushup(int u){
	pushup(tree[u] , tree[u*2] , tree[u*2+1]);
}

void build(int u, int l, int r){
	tree[u].l = l , tree[u].r = r;
	if(l == r){               //递归条件  + 初始化 
		tree[u].sum  = a[l];	
		tree[u].lmax = a[l];
		tree[u].rmax = a[l];
		tree[u].tmax = a[l];
		return;
	}               
	int mid = (l + r) / 2;
	build(u*2, l ,mid);
	build(u*2+1, mid+1, r);
	pushup(u);
}


node query(int u, int l, int r){
	if(tree[u].l >= l && tree[u].r <= r){
		return tree[u];
	}
	int mid = (tree[u].l + tree[u].r) / 2;		//是tree根节点的中间  
	node s1, s2, s;
	
	if(r <= mid) return s1 = query(u*2, l, r);
	if(l > mid)  return s2 = query(u*2+1, l, r);       
	
	s1 = query(u*2, l, r);	
	s2 = query(u*2+1, l, r);
	
	pushup(s, s1, s2);
		
	return s;
}


void modify(int u, int x, int v){
	if(tree[u].l == x && tree[u].r == x){
		tree[u].sum =  v;
		tree[u].lmax = v;
		tree[u].rmax = v;
		tree[u].tmax = v;
	}
	else{
		int mid = ( tree[u].l + tree[u].r ) / 2;
		if(x <= mid) modify(u*2, x, v);
		else modify(u*2+1, x, v);
		pushup(u);
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	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);
			node tmp = query(1, x, y);
			cout<< tmp.tmax <<endl;
		}
		else{
			modify(1, x, y);
		}
	}
	return 0;
}



2、acw 246 线段树 + 差分 + gcd

题面:给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

  1. C l r d,表示把 $A[l],A[l+1],…,A[r] $都加上 d。
  2. Q l r,表示询问 $A[l],A[l+1],…,A[r] $的最大公约数(GCD)。

对于每个询问,输出一个整数表示答案。

区间修改 + 区间查询 —> 差分的单点修改 + 区间查询
推导:
g c d ( a [ l ] , a [ l + 1 ] , . . , a [ R ] ) = g c d ( a [ l ] , a [ l + 1 ] − a [ l ] , a [ l + 2 ] − a [ l + 1 ] , . . . , a [ R ] − a [ R − 1 ] ) gcd(a[l], a[l+1], .. , a[R]) = gcd(a[l], a[l+1]-a[l] , a[l+2]-a[l+1] ,... ,a[R] - a[R-1]) gcd(a[l],a[l+1],..,a[R])=gcd(a[l],a[l+1]a[l],a[l+2]a[l+1],...,a[R]a[R1])
令$ c[i] = a[i] - a[i-1]$
则原式 $= gcd(a[l] , (c[l+1],…,c[R])) $
用差分数组 c 来作为树状数组的原数组 ,求 gcd就是求 L+1 ~ R的 gcd
a数组的区间修改:a[l] ~ a[R] 加 $d:c[l]+d, d[R+1]-d $
则求 $a[i] = sum(c[1] ~ c[i]) $
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 510000;

ll n,m;
ll a[N] , c[N] ;
char ch;
struct node{
	ll l,r;
	ll sum,  d;        //d:区间的 gcd 
}tree[N*4];

ll gcd(ll x,ll y){
	if(y==0) return x;
	if(x<0) x *= -1;
	if(y<0) y *= -1;
	if(x < y) return gcd(y,x);
	return gcd(y, x%y);
}

void pushup(ll u){
	tree[u].sum = tree[u*2].sum + tree[u*2+1].sum;
	tree[u].d = gcd(tree[u*2].d , tree[u*2+1].d);
}

void build(ll u, ll l, ll r){
	tree[u].l = l, tree[u].r = r;
	if(l == r){
		tree[u].sum = c[l];
		tree[u].d = c[l];
		return;
	}
	ll mid = (l+r)/2;
	build(u*2, l, mid);
	build(u*2+1, mid+1, r);
	pushup(u);
}

void modify(ll u, ll x, ll val){
	if(tree[u].l == x && tree[u].r == x){
		tree[u].sum += val;
		tree[u].d += val;
	}
	else{
		ll mid = (tree[u].l + tree[u].r) / 2;
		if(x<=mid) modify(u*2, x, val);
		else modify(u*2+1, x, val);
		pushup(u);
	}
}

node query(ll u, ll l, ll r){
	if(tree[u].l >= l && tree[u].r <= r){
		return tree[u];
	}
	ll mid = (tree[u].l + tree[u].r) / 2;
	node tmp = {l,r,0,0}, s1 = {l,r,0,0}, s2 = {l,r,0,0};
	if(l <= mid)  s1 = query(u*2, l , r);
	if(r > mid) s2 = query(u*2+1, l , r);
	tmp.sum = s1.sum + s2.sum;
	tmp.d = gcd(s1.d , s2.d);
	return tmp;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1; i<=n; i++){
		cin>>a[i];
		c[i] = a[i] - a[i-1];
	}
	build(1,1,n+1);
	while(m--){
		cin>>ch;
		ll l,r,d;
		if(ch == 'C'){
			cin>>l>>r>>d;
			c[l] += d, c[r+1] -= d;
			modify(1, l, d);
			modify(1, r+1, -d);	
		}
		else{
			cin>>l>>r;
			node tmp, tmp2;
			ll k = 0;
			tmp = query(1, l+1, r);
			tmp2 = query(1, 1, l);
			k = tmp.d;
			a[l] = tmp2.sum;
			cout<< gcd(a[l] , k) <<endl;
		}
	}
	return 0;
}

3、acw 243 线段树 (经典pushdown操作)

pushdown操作: (区间修改 + 查询 都要)
1、sum  区间和:考虑当前结点和所有子节点的所有标记的当前区间和(并不考虑祖先结点标记)
2、add:懒标记上一次区间修改只传到了当前层,未往下传播
    
left.add += root.add;
left.sum += (left.r - right.l + 1) * root.add;   //长度 * add = 总和
right.add += root.add;
right.sum += (right.r - right.l + 1) * root.add;

void push_down(int u){
	auto &root = tree[u];
	auto &left = tree[u*2];
	auto &right = tree[u*2+1];
	if(root.lazy){
		left.lazy += root.lazy;
		left.sum += (left.r - left.l + 1) * root.lazy;
		right.lazy += root.lazy;
		right.sum += (right.r - right.l + 1) * root.lazy;
		root.lazy = 0;
	}
}

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

  1. C l r d,表示把 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],,A[r]都加上 d。
  2. Q l r,表示询问数列中第$ l∼r$个数的和。

对于每个询问,输出一个整数表示答案

区间修改 + 区间查询 pushdown登场

AC代码
// 区间修改 + 区间查询  
#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 210000;

ll n,m,a[N];
char ch;
struct node{
	int l,r;
	ll sum, lazy;
}tree[N * 4];

void pushup(int u){
	tree[u].sum = tree[u*2].sum + tree[u*2+1].sum;	
}

void push_down(int u){
	auto &root = tree[u];
	auto &left = tree[u*2];
	auto &right = tree[u*2+1];
	if(root.lazy){
		left.lazy += root.lazy;
		left.sum += (left.r - left.l + 1) * root.lazy;
		right.lazy += root.lazy;
		right.sum += (right.r - right.l + 1) * root.lazy;
		root.lazy = 0;
	}
}

void build(ll u, ll l, ll r){
	tree[u].l = l, tree[u].r = r;
	if(l == r){
		tree[u].sum = a[l];
		tree[u].lazy = 0;
		return;
	}
	int mid = (l + r) / 2;
	build(u*2, l , mid);
	build(u*2+1, mid + 1 , r);
	pushup(u);
}

ll query(ll u , ll l , ll r){
	if(tree[u].l >= l && tree[u].r <= r){
		return tree[u].sum;
	}
	
	push_down(u);   //由于要查询子区间,因此必须由父节点对子节点产生影响  
	
	int mid = (tree[u].l + tree[u].r) / 2;
	ll s=0,s1=0,s2=0;
	if(l <=mid) s1 = query(u*2, l, r);
	if(r > mid) s2 = query(u*2+1, l, r);
	s = s1 + s2;
	return s;
}

void modify(int u, int l, int r, int val){
	if(tree[u].l >= l && tree[u].r <= r && r-l > 0){ 		//是一个区间  
		tree[u].sum += (tree[u].r - tree[u].l + 1) * val;
		tree[u].lazy += val;
		return;
	}
	
	push_down(u);      //想要往下分裂,必须 pushdown 
	
	int mid = (tree[u].l + tree[u].r) / 2;
	if(l <= mid) modify(u*2, l , r, val);
	if(r > mid) modify(u*2+1, l, r, val);
	
	pushup(u);         // 修改了子区间的sum,要同时更新父节点  
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	while(m--){
		cin>>ch;
		ll l,r,d;
		if(ch == 'C'){
			cin>>l>>r>>d;
			modify(1, l , r, d);
		}
		else{
			cin>>l>>r;
			cout<< query(1, l, r)<<endl;
		}
	}
	return 0;
} 

3、acw 1277维护序列 线段树 + add标记 + mul标记

题面

有如下三种操作形式:

  1. 把数列中的一段数全部乘一个值;
  2. 把数列中的一段数全部加一个值;
  3. 询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。
输入格式

第一行两个整数 N 和 P;

第二行含有 N 个非负整数,从左到右依次为 a 1 , a 2 , … , a N a_1,a_2,…,a_N a1,a2,,aN

第三行有一个整数 M,表示操作总数;

从第四行开始每行描述一个操作,输入的操作有以下三种形式:

  • 操作 1:1 t g c,表示把所有满足 t ≤ i ≤ g 的 a i t≤i≤g 的 a_i tigai 改为 a i × c a_i×c ai×c
  • 操作 2:2 t g c,表示把所有满足 t ≤ i ≤ g 的 a i t≤i≤g 的 a_i tigai改为 a i + c a_i+c ai+c
  • 操作 3:3 t g,询问所有满足$ t≤i≤g 的 a_i$ 的和模 P 的值。

同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

同时涉及乘法与加法的懒标记,需要分类讨论出正确的执行顺序!
思路:
  • a ∗ x + b a*x + b ax+b
  • ( a + b ) ∗ x = > a ∗ x + b ∗ x (a+b)*x => a*x+b*x (a+b)x=>ax+bx

在往下传递懒标记的过程中,情况一直接先乘后加即可,而情况二先乘后加,加的是 b*x;

因此,易得操作策略:

  • 乘法操作: m u l = m u l ∗ x mul = mul * x mul=mulx , a d d = a d d ∗ x add = add * x add=addx
  • 加法操作: a d d = a d d + b add = add + b add=add+b

在pushdown的过程中:

  • 先乘 mul 再加 add
  • mul标记的更新:sum、mul、add都乘
  • add标记的更新:add、sum直接加
AC代码:
#include <bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 110000;

ll n,mod;
ll a[N], m;
struct node{
	ll l,r;
	ll sum, add, mul;
}tree[N*4];


void pushup(ll u){
	tree[u].sum = (tree[u*2].sum + tree[u*2+1].sum) % mod;
}

void pushdown(ll u){
	ll mul = tree[u].mul;
	ll add = tree[u].add;
	if(tree[u].mul!=1){
		tree[u*2].sum = tree[u*2].sum * mul % mod;
		tree[u*2+1].sum = tree[u*2+1].sum * mul % mod;
		tree[u*2].mul = tree[u*2].mul * mul % mod;
		tree[u*2+1].mul = tree[u*2+1].mul * mul  % mod;
		tree[u*2].add = tree[u*2].add * mul % mod;               //!
		tree[u*2+1].add = tree[u*2+1].add * mul  % mod;
		tree[u].mul = 1;
	}
	if(tree[u].add){
		tree[u*2].sum = (tree[u*2].sum + (tree[u*2].r - tree[u*2].l + 1) * add  % mod)  % mod;
		tree[u*2+1].sum = (tree[u*2+1].sum + (tree[u*2+1].r - tree[u*2+1].l + 1) * add % mod)  % mod;
		tree[u*2].add = (tree[u*2].add + add)  % mod;
		tree[u*2+1].add = (tree[u*2+1].add + add) % mod;
		tree[u].add = 0;
	}
}

void build(ll u , ll l , ll r){
	tree[u].l = l, tree[u].r = r;
	if(l == r){
		tree[u].sum = a[l];
		tree[u].mul = 1;
		tree[u].add = 0;
		return;
	}
	ll mid = (l + r) / 2;
	build(u*2, l, mid);
	build(u*2+1, mid+1, r);
	pushup(u);
	tree[u].mul = 1;
	tree[u].add = 0;
}


void multiply(ll u, ll l, ll r, ll val){
	if(tree[u].l >= l && tree[u].r <= r){
		tree[u].sum = tree[u].sum * val % mod;
		tree[u].mul = tree[u].mul * val % mod;
		tree[u].add = tree[u].add * val % mod ;
		return;
	}
	pushdown(u);					//想要向下操作,必须传递标记  
	ll mid = (tree[u].l + tree[u].r) / 2;
	if(l <= mid) multiply(u*2, l, r, val);
	if(r > mid) multiply(u*2+1, l, r, val);
	pushup(u);
}


void add_val(ll u, ll l, ll r, ll val){
	if(tree[u].l >= l && tree[u].r <= r){
		tree[u].sum = ( tree[u].sum + (tree[u].r - tree[u].l + 1 ) * val  % mod ) % mod;
		tree[u].add = ( tree[u].add + val ) % mod;
		return;
	}
	pushdown(u);
	ll mid = (tree[u].l + tree[u].r) / 2;
	if(l <= mid) add_val(u*2, l , r, val);
	if(r > mid) add_val(u*2+1, l, r, val);
	pushup(u);
}


ll query(ll u, ll l, ll r){
	if(tree[u].l >= l && tree[u].r <= r){
		return tree[u].sum % mod;
	}
	pushdown(u);
	ll mid = (tree[u].l + tree[u].r) / 2;
	ll s=0,s1=0,s2=0;
	if(l <= mid) s1 = query(u*2, l, r);
	if(r > mid) s2 = query(u*2+1, l, r);
	return (s1 + s2) % mod;
}

int main(){
	cin>>n>>mod;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m;
	build(1,1,n);
	while(m--){
		int op,t,g,c;
		cin>>op>>t>>g;
		if(op==1){
			cin>>c;
			multiply(1, t, g, c);
		}
		else if(op == 2){
			cin>>c;
			add_val(1, t, g, c);
		}
		else{
			cout<< query(1, t, g) % mod <<endl;
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值