线段树总结及例题 - 【数据结构】

线段树

最近在学线段树,这个东西真的是有点难度,看了我整整两天才理解他的意思,其中最难的我觉得就是对懒惰标记的理解吧。

引言:
有一些题目,总是跟区间相关,比如求区间上的最大值与最小值,但是我们要的是n次询问,每次求最大值最小值,暴力就会t,不解释,可以用RMQ,也可以用线段树做。再比如说,每次询问一段区间的和,前缀和对吧?的确可以做。那我修改一下,每次询问有两种情况,即将某一区间的每个数加上一个值,或者询问区间和。这时候前缀和就不好用了,你的预处理在值改变之后,你又要再去处理一次,同样预处理就毫无意义。所以说线段树是多么的强大,但是也不好写这也是真的。

什么是线段树:
线段树是一棵二叉树,而且是一棵平衡二叉树,其实我更喜欢叫它区间树。二叉树,大家都知道,先序遍历创建二叉树,用一个递归函数来实现,建造当前父节点,然后递归创建左孩子节点,再递归创建右孩子节点。而线段树的创建与他有很大的相似度,毕竟大家都是二叉树嘛。先说一下线段树的结构,就是一个结构体,我们直接开一个静态的数组空间,不需要像二叉树那样建一个链式结构,虽然节省空间,但是我们其实没有必要那样。

建树:
首先我们创建一个结构体,里面包含了我们要维护的信息,和lazy值。结构数组大小为输入数据数组大小的4倍。

typedef long long ll;
int n,m;
ll a[maxn];
struct node{
	ll sum;
	ll lazy;
}tree[maxn*4+10];

接下来就是递归函数了,由于线段树,每个节点就是代表一个区间,所以我们将区间分成两半,即[l,mid]和[mid+1,r],这就形成了一棵二叉树的结构。二叉树,父节点编号为i,则左孩子为i2,右孩子为i2+1。由此写出递归函数。

void buildTree(int root,int l,int r){
	tree[root].lazy=0;
	if(l==r){
		tree[root].sum=a[l];
		return;  //这个return很重要 不要忘记了,因为默认的函数结束才return 
	}
	int mid=l+r>>1;
	buildTree(root<<1,l,mid);
	buildTree(root<<1|1,mid+1,r);
	update(root);
}

哦,对了,这里还有个update函数,他的作用是更新当前节点的值,你想一下如果我们有一段区间,那它的和是不是等于两段区间的和。而且我们这个递归函数的出口就是叶子节点,这时候我们通过叶子节点的值,回溯来求出每个父亲节点的值,这一步就体现在这个函数上面。

void update(int root){
	tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
}

lazy标记:
人都是lazy的,计算机也一样,所以我们要尽量减少程序运行所花费的时间。lazy标记就是我们要改变一段区间和的时候,我们可能想到的就是找到叶子节点,然后将值加上去,然后更新所有父节点的值,然而这样做的时间消耗是巨大的。所以我们可以偷一下懒,并不直接去更新所有的值,每次只更新需要用到的值,访问的时候也一样,不访问我们就不更新。总之,lazy标记的含义就是延迟更新,在我们不需要访问区间内部时就保留lazy标记的值,如果需要访问内部的时候,我们要先将lazy标记下推, 因为可能lazy标记还需要继续往下走。最后你看到的树应该是你需要修改的区间上面的部分已经更新过了,但是它下面的部分都没有改变,但是保存了上一层推下来的lazy值。

但是,使用lazy标记要注意很多细节,首先建树的时候要初始化为0。其次,我们需要一个函数pushdown,当我们修改区间上的值和访问区间和的时候,每次要调用此函数将lazy值向下推一层。

void pushdown(int root,int l,int r){
	if(tree[root].lazy==0) return;
	int lchild=root<<1,rchild=root<<1|1;
	int mid=r+l>>1;
	tree[lchild].sum+=tree[root].lazy*(mid-l+1);
	tree[rchild].sum+=tree[root].lazy*(r-mid);
	tree[lchild].lazy+=tree[root].lazy;
	tree[rchild].lazy+=tree[root].lazy;
	tree[root].lazy=0;
}

接下来我们给出第一道具体的题目包含操作:
区间求和与区间修改

题目链接:A Simple Problem with Integers

Time Limit: 5000MS
Memory Limit: 131072K
Case Time Limit: 2000MS

Description

You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

Input

The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, … , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
“C a b c” means adding c to each of Aa, Aa+1, … , Ab. -10000 ≤ c ≤ 10000.
“Q a b” means querying the sum of Aa, Aa+1, … , Ab.

Output

You need to answer all Q commands in order. One answer in a line.

Sample Input

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output

4
55
9
15
Hint

The sums may exceed the range of 32-bit integers.

线段树最终代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<math.h>
#include<queue>
using namespace std;
#define N 100001
#define maxn 100004
typedef long long ll;
const int inf=0x3f3f3f3f;	
int n,m;
ll a[maxn];
struct node{
	ll sum;
	ll lazy;
}tree[maxn*4+10];
void update(int root){
	tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
}
void pushdown(int root,int l,int r){
	if(tree[root].lazy==0) return;
	int lchild=root<<1,rchild=root<<1|1;
	int mid=r+l>>1;
	tree[lchild].sum+=tree[root].lazy*(mid-l+1);
	tree[rchild].sum+=tree[root].lazy*(r-mid);
	tree[lchild].lazy+=tree[root].lazy;
	tree[rchild].lazy+=tree[root].lazy;
	tree[root].lazy=0;
}
void buildTree(int root,int l,int r){
	tree[root].lazy=0;
	if(l==r){
		tree[root].sum=a[l];
		return;   
	}
	int mid=l+r>>1;
	buildTree(root<<1,l,mid);
	buildTree(root<<1|1,mid+1,r);
	update(root);
}
ll ask(int root,int l,int r,int al,int ar){
	if(al==l&&ar==r){
		return tree[root].sum;
	}
	int mid=l+r>>1;
	pushdown(root,l,r);
	if(ar<=mid) return ask(root<<1,l,mid,al,ar);
	else if(al>=mid+1) return ask(root<<1|1,mid+1,r,al,ar);
	else return ask(root<<1,l,mid,al,mid)+ask(root<<1|1,mid+1,r,mid+1,ar);
}
void change(int root,int l,int r,int al,int ar,int val){
	if(l==al&&r==ar){
		tree[root].sum+=val*(r-l+1);
		tree[root].lazy+=val;
		return;
	}
	pushdown(root,l,r);
	int mid=l+r>>1;
	if(ar<=mid) change(root<<1,l,mid,al,ar,val);
	else if(al>=mid+1) change(root<<1|1,mid+1,r,al,ar,val);
	else{
		change(root<<1,l,mid,al,mid,val);
		change(root<<1|1,mid+1,r,mid+1,ar,val);
	}
	update(root);
}
int main(){
	char x;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	getchar();
	buildTree(1,1,n);
	while(m--){
		scanf("%c",&x);
		if(x=='Q'){
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%lld\n",ask(1,1,n,l,r));
		}
		else{
			int l,r,val;
			scanf("%d%d%d",&l,&r,&val);
			change(1,1,n,l,r,val);
		}
		getchar();
	}
	return 0;
}

接下来我们给出第二道题目包含操作:
区间求最值与单点修改

Hdu 1754
在这里插入图片描述
ac代码:

#include<stdio.h>
#include<string.h>
#include<queue>
#include<iostream>
typedef long long ll;
const int maxn=200005;
const int inf=0x3f3f3f3f;
using namespace std;
int n,m;
ll a[maxn];
struct node{
	ll ans;
}tree[maxn*4+100];
void update(int root){
	tree[root].ans=max(tree[root<<1].ans,tree[root<<1|1].ans);
}
void build_tree(int root,int l,int r){
	if(l==r){
		tree[root].ans=a[l];
		return;
	}
	int mid=l+r>>1;
	build_tree(root<<1,l,mid);
	build_tree(root<<1|1,mid+1,r);
	update(root);
}
ll query(int root,int l,int r,int ql,int qr){
	if(l==ql&&r==qr){
		return tree[root].ans;
	}
	int mid=l+r>>1;
	if(qr<=mid) return query(root<<1,l,mid,ql,qr);
	else if(ql>=mid+1) return query(root<<1|1,mid+1,r,ql,qr);
	else return max(query(root<<1,l,mid,ql,mid),query(root<<1|1,mid+1,r,mid+1,qr));
}
void change(int root,int l,int r,int pos,ll val){
	if(l==r){
		tree[root].ans=val;
		return;
	}
	int mid=l+r>>1;
	if(pos<=mid) change(root<<1,l,mid,pos,val);
	else change(root<<1|1,mid+1,r,pos,val);
	update(root);
}
int main(){
	ll l,r;
	char s;
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		getchar();
		build_tree(1,1,n);
		while(m--){
			scanf("%c%lld%lld",&s,&l,&r);
			if(s=='Q'){
				printf("%lld\n",query(1,1,n,l,r));
			}
			else{
				change(1,1,n,l,r);
			}
			getchar();
		}
	}
	
	return 0;
}

最后我们给出第三道提目:
包含操作乘法与加法并存求区间和:
其实他的思路与加法一样,但是有个不同的地方,就是乘法标记下放得时候,同时要将加法标记也乘一下。
在这里插入图片描述
所以进阶pushdown就是这样的:

void pushdown(int root,int l,int r){
	int lc=root<<1,rc=root<<1|1;
	int mid=l+r>>1;
	if(tree[root].lazyc!=1){
		tree[lc].sum=(tree[lc].sum%p*tree[root].lazyc%p)%p;
		tree[rc].sum=(tree[rc].sum%p*tree[root].lazyc%p)%p;
		tree[lc].lazyj=(tree[root].lazyc%p*tree[lc].lazyj%p)%p;
		tree[rc].lazyj=(tree[root].lazyc%p*tree[rc].lazyj%p)%p;
		tree[lc].lazyc=tree[lc].lazyc*tree[root].lazyc%p;
		tree[rc].lazyc=tree[rc].lazyc*tree[root].lazyc%p;
		tree[root].lazyc=1;
	}
	if(tree[root].lazyj){
		tree[lc].sum+=tree[root].lazyj*(mid-l+1)%p;
		tree[rc].sum+=tree[root].lazyj*(r-mid)%p;
		tree[lc].lazyj+=tree[root].lazyj;
		tree[rc].lazyj+=tree[root].lazyj;
		tree[root].lazyj=0;
	}
}

题目:
题目链接:洛谷 P2023

题目描述
老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。 有长为N的数列,不妨设为a1,a2,…,aN 。有如下三种操作形式:
(1)把数列中的一段数全部乘一个值;
(2)把数列中的一段数全部加一个值;
(3)询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模P的值。

输入格式
第一行两个整数N和P(1≤P≤1000000000)。
第二行含有N个非负整数,从左到右依次为a1,a2,…,aN, (0≤ai≤1000000000,1≤i≤N)。
第三行有一个整数M,表示操作总数。
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作1:“1 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai×c(1≤t≤g≤N,0≤c≤1000000000)。
操作2:“2 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai+c (1≤t≤g≤N,0≤c≤1000000000)。
操作3:“3 t g”(不含双引号)。询问所有满足t≤i≤g的ai的和模P的值 (1≤t≤g≤N)。
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

输出格式
对每个操作3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。

输入 #1
7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7

输出 #1
2
35
8

说明/提示
【样例说明】

初始时数列为(1,2,3,4,5,6,7)。

经过第1次操作后,数列为(1,10,15,20,25,6,7)。

对第2次操作,和为10+15+20=45,模43的结果是2。

经过第3次操作后,数列为(1,10,24,29,34,15,16}

对第4次操作,和为1+10+24=35,模43的结果是35。

对第5次操作,和为29+34+15+16=94,模43的结果是8。

测试数据规模如下表所示

数据编号 1 2 3 4 5 6 7 8 9 10

N= 10 1000 1000 10000 60000 70000 80000 90000 100000 100000

M= 10 1000 1000 10000 60000 70000 80000 90000 100000 100000

ac代码:

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int n,p,m,t;
int a[maxn];
struct node{
	ll sum;
	ll lazyc;
	ll lazyj;
}tree[maxn*4+10];
void update(int root){
	tree[root].sum=(tree[root<<1].sum+tree[root<<1|1].sum)%p;
}
void pushdown(int root,int l,int r){
	int lc=root<<1,rc=root<<1|1;
	int mid=l+r>>1;
	if(tree[root].lazyc!=1){
		tree[lc].sum=(tree[lc].sum%p*tree[root].lazyc%p)%p;
		tree[rc].sum=(tree[rc].sum%p*tree[root].lazyc%p)%p;
		tree[lc].lazyj=(tree[root].lazyc%p*tree[lc].lazyj%p)%p;
		tree[rc].lazyj=(tree[root].lazyc%p*tree[rc].lazyj%p)%p;
		tree[lc].lazyc=tree[lc].lazyc*tree[root].lazyc%p;
		tree[rc].lazyc=tree[rc].lazyc*tree[root].lazyc%p;
		tree[root].lazyc=1;
	}
	if(tree[root].lazyj){
		tree[lc].sum+=tree[root].lazyj*(mid-l+1)%p;
		tree[rc].sum+=tree[root].lazyj*(r-mid)%p;
		tree[lc].lazyj+=tree[root].lazyj;
		tree[rc].lazyj+=tree[root].lazyj;
		tree[root].lazyj=0;
	}
}
void build_tree(int root,int l,int r){
	tree[root].lazyc=1;
	tree[root].lazyj=0;
	if(l==r){
		tree[root].sum=a[l]%p;
		return;
	}
	int mid=l+r>>1;
	build_tree(root<<1,l,mid);
	build_tree(root<<1|1,mid+1,r);
	update(root);
}
void add_num(int root,int l,int r,int ql,int qr,int add){
	if(l==ql&&r==qr){
		tree[root].sum=(tree[root].sum%p+add*(r-l+1)%p)%p;
		tree[root].lazyj=(tree[root].lazyj+add)%p;
		return;
	}
	pushdown(root,l,r);
	int mid=l+r>>1;
	if(qr<=mid) add_num(root<<1,l,mid,ql,qr,add);
	else if(ql>=mid+1) add_num(root<<1|1,mid+1,r,ql,qr,add);
	else{
		add_num(root<<1,l,mid,ql,mid,add);
		add_num(root<<1|1,mid+1,r,mid+1,qr,add);
	}
	update(root);
}
void mul_num(int root,int l,int r,int ql,int qr,int mul){
	if(l==ql&&r==qr){
		tree[root].sum=(tree[root].sum*mul)%p;
		tree[root].lazyc=tree[root].lazyc*mul%p;
		tree[root].lazyj=(tree[root].lazyj*mul)%p;
		return;
	}
	pushdown(root,l,r);
	int mid=l+r>>1;
	if(qr<=mid) mul_num(root<<1,l,mid,ql,qr,mul);
	else if(ql>=mid+1) mul_num(root<<1|1,mid+1,r,ql,qr,mul);
	else{
		mul_num(root<<1,l,mid,ql,mid,mul);
		mul_num(root<<1|1,mid+1,r,mid+1,qr,mul);
	}
	update(root);
}
ll query(int root,int l,int r,int ql,int qr){
	if(l==ql&&r==qr){
		return tree[root].sum%p;
	}
	pushdown(root,l,r);
	int mid=l+r>>1;
	if(qr<=mid) return query(root<<1,l,mid,ql,qr)%p;
	else if(ql>=mid+1) return query(root<<1|1,mid+1,r,ql,qr)%p;
	else return (query(root<<1,l,mid,ql,mid)%p+query(root<<1|1,mid+1,r,mid+1,qr)%p)%p;
}
int main(){
	scanf("%d%d",&n,&p);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	scanf("%d",&m);
	build_tree(1,1,n);
	while(m--){
		scanf("%d",&t);
		if(t==1){
			int l,r,mul;
			scanf("%d%d%d",&l,&r,&mul);
			mul_num(1,1,n,l,r,mul);
		}
		else if(t==2){
			int l,r,add;
			scanf("%d%d%d",&l,&r,&add);
			add_num(1,1,n,l,r,add);
		}
		else{
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%lld\n",query(1,1,n,l,r));
		}
	}
	return 0;
}
发布了43 篇原创文章 · 获赞 56 · 访问量 5128

猜你喜欢

转载自blog.csdn.net/tran_sient/article/details/97002351
今日推荐