线段树属于完全二叉树,单个数据存在叶子节点中,非叶子节点存储区域内最大值,区间和。
如下是插入[1, 2, 3, 4, 5, 6, 7]后的结果

建树
比如,有n个数据,调用 build [1, n],那么叶子节点就有n个数据,每个节点的左右边界都会如上图。当左右边界相等时,说明到了叶子节点,可以结束这一过程了。建树过程只需要设置左右边界。其他数据可以不用管。
插入数据
从根节点出发,找到左右边界相等的目标叶子节点,沿途需要更新最大值,这个非常好理解。更新区间和,这个也比较好理解,插入数据阶段不需要考虑区间和变少的过程(变少的过程会在更新数据中得以解决),因为插入过程是挨个往叶子节点存放数据的,区间和只会增大,沿途的节点都更新区间和。
更新数据
这个过程类似插入数据的过程,不同之处在于,此过程会考虑区间和“变小”的过程,比如9变成-9,那么沿途区间包含目标节点的节点的区间和,最大值,都需要更新。
区间求和
对于【1,4】这种区间,某节点的左右区间刚好为所求,那么找到这个节点就行,但是存在【2,3】这种“跨节点”的区间,所以使得区间求和变得复杂了一点点。但是通过递归调用,找到“合适的”区间组合,随即完成区间求和。
区间求最大值
对于【1,4】这种区间,某节点的左右区间刚好为所求,那么找到这个节点就行,但是存在【2,3】这种“跨节点”的区间,所以使得区间求和变得复杂了一点点。但是通过递归调用,找到“合适的”区间组合,随即完成区间求最大值。
区间修改
线段树的区间修改是其精髓所在。
比如,将[1, 9]中的每个元素都加+10,如果是挨个挨个按照单点更新的方式,时间复杂度就是nlogn了,这还是只是一次区间更新,如果是m次,那么时间复杂度则是m * nlogn。显然是不行的。需要用到区间修改。并且再考虑一个问题,第一次,更新[1, 9],第二次更新还是[1, 9],那不是重复操作了吗?有没有一种“去重”,或者延迟更新(等到查询到那一块区域才更新)的操作。
那么就是引入懒标记了,懒标记就有“去重”和延迟更新的功能。
懒标记的过程是:当进行区间修改的时候,先标记着,不实际更新,等到查询的时候,才更新数据,当更新数据的时候,把向下传递的节点的懒标记清除。那么如何标记呢?
有三种情况。
当前区间[3, 4] 包含于要修改的区间 [3, 7],在[3, 4]这个节点做一个标记。
当前区间[3, 4] 不相交 区间 [5, 7],跳过。
当前区间[2,4] 外交于 区间[4,7] ,此时,将区间一分为二,左子树判断,右子树判断(这里不断的递归,最终会走到1,2种情况)。
Java版本-模板-改进(超时,频繁的新建对象,导致超时,时间复杂度应当是没问题,有时间写一个数组的版本)
public static class TreeNode {
int val = 0, l = 0, r = 0;
int sum = 0, max = 0, lazy = 0;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public void build(TreeNode node, int l, int r) {
node.l = l;
node.r = r;
if (l == r) return;
node.left = new TreeNode(0);
node.right = new TreeNode(0);
int mid = (l + r) >> 1;
build(node.left, l, mid);
build(node.right, mid + 1, r);
}
public void insert(TreeNode node, int index, int value) {
node.sum += value;
node.max = Math.max(node.max, value);
if (node.l == node.r) {
node.val = value;
return;
}
if (node.left.r <= index) {
insert(node.left, index, value);
} else {
insert(node.right, index, value);
}
}
// 单点更新
public void update(TreeNode node, int index, int value) {
if (node.l == node.r) {
node.max = node.sum = node.val = value;
return;
}
if (node.left.r >= index) {
update(node.left, index, value);
} else {
update(node.right, index, value);
}
node.sum = node.left.sum + node.right.sum;
node.max = Math.max(node.left.max, node.right.max);
}
// 区间更新
public void updateArea(TreeNode node, int l, int r, int k) {
if (node.l > r || node.r < l) {
// 毫不相干的两个交集
return;
} else if (node.l >= l && node.r <= r) {
// 目标区间包含,当前区间
node.sum += (node.r - node.l + 1) * k;
// 如果不是叶子节点,进行懒惰标记
if (node.r > node.l) {
node.lazy += k;
}
} else {
//// 目标区间外交当前区间
//// 左边可能相交部分
//node.left.lazy += node.lazy;
//node.right.lazy += node.lazy;
//
//// 左子树会用到的区间,外交,说明左子树的右边界一定包含,
//node.left.sum += node.lazy * (node.left.r - node.l + 1);
//// 右子树会用到的区间
//node.right.sum += node.lazy * (node.r - node.left.r);
//
//// 清楚标记
//node.lazy = 0;
// 向下传递
pushDown(node);
updateArea(node.left, l, r, k);
// 右边相交部分
updateArea(node.right, l, r, k);
// 更新当前节点的值
node.sum += node.left.sum + node.right.sum;
}
}
// 向下传递
public void pushDown(TreeNode node) {
// 目标区间外交当前区间
// 左边可能相交部分
node.left.lazy += node.lazy;
node.right.lazy += node.lazy;
// 左子树会用到的区间,外交,说明左子树的右边界一定包含,
node.left.sum += node.lazy * (node.left.r - node.l + 1);
// 右子树会用到的区间
node.right.sum += node.lazy * (node.r - node.left.r);
// 清除标记
node.lazy = 0;
}
// 区间和
public int getSum(TreeNode node, int l, int r) {
if (node.l > r || node.r < l) {
return 0;
} else if (node.l >= l && node.r <= r) {
return node.sum;
}
int leftSum = 0;
//if (node.left.r >= l) {} // 为什么这么写,因为左左子树的右叶子节点更容易满足条件,可以反向想一下不成立的条件
pushDown(node);
leftSum += getSum(node.left, l, r);
//if (node.right.l <= r) {} // 为什么这么写,因为右右子树的左叶子节点更容易满足条件
leftSum += getSum(node.right, l, r);
return leftSum;
}
public int getMax(TreeNode node, int l, int r) {
if (node.l >= l && node.r == r) {
return node.max;
}
if (node.l == node.r) {
return node.val;
}
int maxTemp = Integer.MIN_VALUE;
if (node.left.r >= l) {
maxTemp = Math.max(maxTemp, getMax(node.left, l, r));
}
if (node.right.l <= r) {
maxTemp = Math.max(maxTemp, getMax(node.right, l, r));
}
return maxTemp;
}
public void sequence(TreeNode node) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(node);
while(!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
System.out.print("[ " + cur.l + ", " + cur.r + " ] ");
System.out.print("cur : " + cur.val + ", sum : " + cur.sum + " max : " + cur.max + " | ");
if (cur.left != null) queue.add(cur.left);
if (cur.right != null) queue.add(cur.right);
}
System.out.println();
}
}
public static void main(String[] args) {
Question1 q = new Question1();
TreeNode head = new TreeNode(0);
// 验证建树
q.build(head, 1, 7);
q.sequence(head);
System.out.println("===========================");
// 验证插入
for (int i = 1; i <= 7; i++) {
q.insert(head, i, i);
}
q.sequence(head);
System.out.println("===========================");
// 验证更新
for (int i = 1; i <= 7; i++) {
q.update(head, i, -i);
}
q.sequence(head);
System.out.println("===========================");
// // 验证区域和
System.out.println(q.getSum(head, 3, 4));
System.out.println(q.getSum(head, 2, 6));
System.out.println(q.getSum(head, 4, 7));
System.out.println(q.getSum(head, 1, 7));
System.out.println("===========================");
//
// // 验证区域最大值
System.out.println(q.getMax(head, 3, 4));
System.out.println(q.getMax(head, 4, 7));
System.out.println(q.getMax(head, 2, 6));
System.out.println(q.getMax(head, 4, 4));
System.out.println(q.getMax(head, 4, 5));
System.out.println("===========================");
// 验证区间修改
q.updateArea(head, 1, 7, 1);
System.out.println(q.getSum(head, 3, 4));
System.out.println(q.getSum(head, 2, 6));
System.out.println(q.getSum(head, 4, 7));
System.out.println(q.getSum(head, 1, 7));
//q.sequence(head);
}
Java版-模板-初版
public static class TreeNode {
int val = 0, l = 0, r = 0, sum = 0, max = 0;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
// 建树
public void build(TreeNode node, int l, int r) {
node.l = l;
node.r = r;
if (l == r) return;
int mid = (l + r) >> 1;
node.left = new TreeNode(0);
node.right = new TreeNode(0);
build(node.left, l, mid);
build(node.right, mid + 1, r);
}
// 插入数据
public void insert(TreeNode node, int index, int value) {
node.sum += value;
node.max = Math.max(node.max, value);
if (node.l == node.r) {
node.val = value;
return;
}
int mid = (node.l + node.r) >> 1;
if (index <= mid) {
insert(node.left, index, value);
} else {
insert(node.right, index, value);
}
}
// 更新数据
public void update(TreeNode node, int index, int value) {
if (node.l == node.r) {
node.max = node.sum = node.val = value;
return;
}
int mid = (node.l + node.r) >> 1;
if (index <= mid) {
update(node.left, index, value);
} else {
update(node.right, index, value);
}
node.sum = node.left.sum + node.right.sum;
node.max = Math.max(node.left.max, node.right.max);
}
// 区间和
public int getSum(TreeNode node, int l, int r) {
if (node.l == l && node.r == r) {
return node.sum;
}
if (node.l == node.r) {
return node.val;
}
int mid = (node.l + node.r) >> 1;
if (r <= mid) {
return getSum(node.left, l, r);
} else if (l >= mid + 1) {
return getSum(node.right, l, r);
} else {
return getSum(node.left, l, mid) + getSum(node.right, mid + 1, r);
}
}
// 区间最大值
public int getMax(TreeNode node, int l, int r) {
if (node.l == l && node.r == r) {
return node.max;
}
if (node.l == node.r) {
return node.val;
}
int mid = (node.l + node.r) >> 1;
if (r <= mid) {
return getMax(node.left, l, r);
} else if (l >= mid + 1) {
return getMax(node.right, l, r);
} else {
return Math.max(getMax(node.left, l, mid), getMax(node.right, mid + 1, r));
}
}
// 区间最小值
public int getMin(TreeNode node, int l, int r) {
if (node.l == l && node.r == r) {
return node.min;
}
if (node.l == node.r) {
return node.val;
}
int mid = (node.l + node.r) >> 1;
if (r <= mid) {
return getMin(node.left, l, r);
} else if (l >= mid + 1) {
return getMin(node.right, l, r);
} else {
return Math.min(getMin(node.left, l, mid), getMin(node.right, mid + 1, r));
}
}
// 层序遍历
public void sequence(TreeNode node) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(node);
while(!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode cur = queue.poll();
System.out.print("[ " + cur.l + ", " + cur.r + " ] ");
System.out.print("cur : " + cur.val + ", sum : " + cur.sum + " max : " + cur.max + " | ");
if (cur.left != null) queue.add(cur.left);
if (cur.right != null) queue.add(cur.right);
}
System.out.println();
}
}
public static void main(String[] args) {
Question1 q = new Question1();
TreeNode head = new TreeNode(0);
// 验证建树
q.build(head, 1, 7);
q.sequence(head);
System.out.println("===========================");
// 验证插入
for (int i = 1; i <= 7; i++) {
q.insert(head, i, i);
}
q.sequence(head);
System.out.println("===========================");
// 验证更新
for (int i = 1; i <= 7; i++) {
q.update(head, i, -i);
}
q.sequence(head);
System.out.println("===========================");
// 验证区域和
System.out.println(q.getSum(head, 3, 4));
System.out.println(q.getSum(head, 4, 7));
System.out.println(q.getSum(head, 1, 7));
System.out.println("===========================");
// 验证区域最大和
System.out.println(q.getMax(head, 2, 4));
System.out.println(q.getMax(head, 4, 7));
System.out.println(q.getMax(head, 1, 7));
System.out.println(q.getMax(head, 3, 5));
System.out.println("===========================");
}
题目:
ProblemE: 操作格子
Time Limit: 1 Memory Limit: 128 MB Submit 0 Solved 0
Description
有n个格子,从左到右放成一排,编号为1-n。
共有m次操作,有3种操作类型:
1.修改一个格子的权值,
2.求连续一段格子权值和,
3.求连续一段格子的最大值。
对于每个2、3操作输出你所求出的结果。
Input
第一行2个整数n,m。
接下来一行n个整数表示n个格子的初始权值。
接下来m行,每行3个整数p,x,y,p表示操作类型,p=1时表示修改格子x的权值为y,p=2时表示求区间[x,y]内格子权值和,p=3时表示求区间[x,y]内格子最大的权值。
Output
有若干行,行数等于p=2或3的操作总数。
每行1个整数,对应了每个p=2或3操作的结果。
Sample Input
4 3 1 2 3 4 2 1 3 1 4 3 3 1 4
Sample Output
6 3
HINT
数据规模与约定
对于20%的数据n <= 100,m <= 200。
对于50%的数据n <= 5000,m <= 5000。
对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。
思路:线段树
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string.h>
#define in(x) scanf("%d",&x)
#define out(x) printf("%d",x)
using namespace std;
typedef long long int ll;
const int maxn=1e5+5;
struct node{
int l,r,max;
ll sum;
}a[400010];
void build(int i,int l,int r)//建树 初始化
{
int mid=(l+r)>>1;
a[i].l=l,a[i].r=r;
a[i].max=0,a[i].sum=0;
if(l==r) return;//如果左右边距相等则不再构建
build(2*i,l,mid);//左孩子 范围为l到mid
build(2*i+1,mid+1,r);//右孩子 范围为mid+1到r
}
void insert(int i,int v,int value)//往下延申更新sum和max
{
a[i].sum+=value;//延申经过的点都加上这个值
if(a[i].max<value) a[i].max=value;//更新最大值
if(a[i].l==a[i].r) return;//左右边距相等时,不再往下延申()
int mid=(a[i].l+a[i].r)>>1;
if(v<=mid){//插入到左孩子
insert(2*i,v,value);
}
else insert(2*i+1,v,value);//插入到右孩子
}
void update(int i,int v,int value)//更改
{
if(v==a[i].l&&v==a[i].r){//如果左右边距都对应相等
a[i].sum=value; // 则找到了对应的value
a[i].max=value;
return;
}
int mid=(a[i].l+a[i].r)>>1;
if(v<=mid){
update(2*i,v,value);//从左孩子出寻找
}
else update(2*i+1,v,value);//从右孩子中寻找
a[i].sum=a[2*i].sum+a[2*i+1].sum;//更新区间和
a[i].max=max(a[2*i].max,a[2*i+1].max);//搜索自上而下,更新自下而上
}
ll getsum(int i,int l,int r)//求区间和
{
if(l==a[i].l&&r==a[i].r) return a[i].sum;//如果左右边距都对应相等,
int mid=(a[i].l+a[i].r)>>1; // 则找到了对应的sum
if(r<=mid){
return getsum(2*i,l,r);//从左孩子出寻找
}
else if(l>mid){//从右孩子中寻找
return getsum(2*i+1,l,r);
}
else{
return getsum(2*i,l,mid)+getsum(2*i+1,mid+1,r);//左右均找
}
}
int getmax(int i,int l,int r)
{
if(l==a[i].l&&r==a[i].r) return a[i].max;//如果左右边距都对应相等,
int mid=(a[i].l+a[i].r)>>1; // 则找到了对应的max
if(r<=mid){
return getmax(2*i,l,r);//从左孩子出寻找
}
else if(l>mid){//从右孩子中寻找
return getmax(2*i+1,l,r);
}
else{
return max(getmax(2*i,l,mid),getmax(2*i+1,mid+1,r));//左右均找
}
}
int main()
{
int n,m,value,a,b,c;
in(n),in(m);
build(1,1,n);//每次从1开始往下延申
for(int i=1;i<=n;i++){
in(value);
insert(1,i,value);//每次依旧是从顶(1)往下延申
}
while(m--){
in(a),in(b),in(c);
if(a==1) update(1,b,c);
else if(a==2) printf("%lld\n",getsum(1,b,c));
else if(a==3) printf("%d\n",getmax(1,b,c));
}
}
1629

被折叠的 条评论
为什么被折叠?



