线段树(持续更新...)

基础概念

可以参考线段树,来大概了解其的内容,感觉他写的好好。
在这个里面,我们大概了解了什么是线段树,那么我们为什么要用线段树,chatGpt讲,其的应用场景主要为:

  • 区间求和:快速查询数组某个区间内元素的和。
  • 区间最值查询:快速查询数组某个区间内的最大值或最小值。
  • 区间更新:高效地将数组某个区间内的所有元素更新为某个值或增加某个值。

但我感觉它说的好笼统啊,因为前两者我好像都可以用其他的方法进行,如前缀和
然后,通过做了一些题目,我感觉其的重点在于动态变化当前数据依赖于前者数据信息
线段树的基础内容为:线段树的构建区间的查询以及区间的更新
线段树的模板操作:build(建造线段树),modify(给线段树加节点)pushdown(向下更新子阶段,用于懒加载,尽量避免使用)pushup(更新根节点)

下面我们用例题来学习:

例题

基础

AcWing 1275. 最大数

题目:

给定一个正整数数列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an,每一个数都在 0∼p−1之间。可以对这列数进行两种操作:

  1. 添加操作:向序列后添加一个数,序列长度变成 n+1;
  2. 询问操作:询问这个序列中最后 L个数中最大的数是多少。

程序运行的最开始,整数序列为空。一共要对整数序列进行 m次操作。写一个程序,读入操作的序列,并输出询问操作的答案。

输入格式

第一行有两个正整数 m,p,意义如题目描述;
接下来 m行,每一行表示一个操作。
如果该行的内容是 Q L,则表示这个操作是询问序列中最后 L个数的最大数是多少;
如果是 A t,则表示向序列后面加一个数,加入的数是 (t+a) mod p。其中,t是输入的参数,a是在这个添加操作之前最后一个询问操作的答案 (如果之前没有询问操作,则 a=0)。
第一个操作一定是添加操作。对于询问操作,L>0且不超过当前序列的长度。

输出格式

对于每一个询问操作,输出一行。该行只有一个数,即序列中最后 L个数的最大数。

数据范围

1 ≤ m ≤ 2 × 1 0 5 1\leq m \leq 2×10^5 1m2×105,
1 ≤ p ≤ 2 × 1 0 9 1\leq p \leq 2×10^9 1p2×109,
1 ≤ t < p 1\leq t \lt p 1t<p

#include<bits/stdc++.h>
using namespace std;
int m,p;
typedef long long LL;
const int N = 2e5;
struct Node{
  int l;
  int r;
  int val;
}tree[N*4+10];
// u表明其当前节点的编号
void build(int u,int l,int r){
  tree[u] = {l,r};
  if(l==r) return;
  int mid = l + r >> 1;
  build(u<<1,l,mid),build(u<<1 | 1 ,mid+1,r);
}
// l是要查找的范围的左节点,r是要查找的范围的右节点
// u表示当前查询的节点编号
int query(int u ,int l,int r){
  if(tree[u].l>=l&&tree[u].r<=r){
    return tree[u].val;
  }
  int mid = tree[u].l + tree[u].r >> 1;
  int v = 0;
  if(l<=mid) v = query(u<<1,l,r);
  if(r>mid) v = max(v,query(u<<1|1,l,r));
  return v;
}
// u表示当前节点的编号
// x表示的是要添加的节点,也就是其l都为x。
void modify(int u,int x,int v){
  if(tree[u].l==x&&tree[u].r==x){
    tree[u].val = v;
  }else{
    int mid = tree[u].l + tree[u].r >> 1;
    if(x <= mid) modify(u<<1,x,v);
    else modify(u<<1|1,x,v);
    // 更新根节点——pushup操作
    tree[u].val = max(tree[u << 1].val, tree[u << 1 | 1].val);
  }
}
int main(){
  // 当前数据的个数
  int n = 0;
  // 表明题目种的a值
  int last = 0;
  // 输入需要输入的次数和p
  scanf("%d%d",&m,&p);
  // 初始化这棵树m即表明了最大值
  build(1,1,m);
  int x;
  char op[2];
  // 操作
  while(m--){
    scanf("%s%d",op,&x);
    if(*op=='Q'){
      last = query(1,n-x+1,n);
      printf("%d\n", last);
    }else{
      modify(1,n+1,((LL)last + x)%p);
      n++;
    }
  }
  return 0;
}

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\leq l\leq r\leq y}{\sum_{i=l}^{r}A[i]} maxxlryi=lrA[i]
2.2 x y,把 A [ x ] A[x] A[x]改成 y。
对于每个查询指令,输出一个整数表示答案。

输入格式

第一行两个整数 N,M。
第二行 N个整数 A[i]。
接下来 M行每行 3个整数 k,x,y,k=1表示查询(此时如果 x>y,请交换 x,y),k=2表示修改。

输出格式

对于每个查询指令输出一个整数表示答案。
每个答案占一行。
数据范围
N ≤ 500000 , M ≤ 100000 N \leq 500000,M \leq 100000 N500000,M100000,
− 1000 ≤ A [ i ] ≤ 1000 −1000 \leq A[i] \leq 1000 1000A[i]1000

分析

首先题目要求即为2点:

  1. 区间和
  2. 单点修改

对于区间和部分,我们进行分析,这也决定了我们Node类的设定,首先我们得最大区间子段和可以来自于其得分段也就是我们之间以mid为分界点进行线段树的分段,这也就是sum属性的由来,还可以是我们这一段的最大子区间之和,也可以是这一段的前半部分或后半部分,这个部分并不是以mid为分界点进行分段的,而是随机的前端和后端,即是属性lmax,rmax.最后也就是我们之前所需要设定的属性,区间的端点l,r,以及我们需要不断根据其子得到的tmax最终答案点。

import java.io.*;

class Node{
    // 区间的左右端点
    int l,r;
    // 最大子区间和
    int tmax;
    // 最大前缀区间和
    int lmax;
    // 最大后缀区间和;
    int rmax;
    // 总和
    int sum;
    void set(int l,int r,int tmax,int lmax,int rmax,int sum){
      this.l = l;
      this.r = r;
      this.tmax = tmax;
      this.lmax = lmax;
      this.rmax = rmax;
      this.sum = sum;
    }
    void setRange(int l,int r){
      this.l = l;
      this.r = r;
    }
    void setMax(int tmax,int lmax,int rmax,int sum){
      this.tmax = tmax;
      this.lmax = lmax;
      this.rmax = rmax;
      this.sum = sum;
    }
  }
class Main{
  
  private static final int N = 500010;
  private static final int[] w = new int[N];
  // 每一个对象都是null,而非一个有效的Node对象
  private static final Node[] tr = new Node[N*4];
  // 建立线段树
  private static void build(int l,int r,int u){
    if(tr[u]==null){
      tr[u] = new Node();
    }
    // 根节点
    if(l==r){
      tr[u].set(l,r,w[r],w[r],w[r],w[r]);
    }else{
      tr[u].setRange(l,r);
      int mid = l + r >> 1;
      // 左子树
      build(l,mid, u << 1);
      //右子树
      build(mid+1,r,u << 1 | 1);
      pushup(u);
    }
  }
  private static void pushup(Node u,Node l,Node r){
    int tmax = Math.max(Math.max(l.tmax, r.tmax), l.rmax + r.lmax);
    int lmax = Math.max(l.sum + r.lmax, l.lmax);
    int rmax = Math.max(r.sum + l.rmax, r.rmax);
    int sum = l.sum + r.sum;
    u.setMax(tmax,lmax,rmax,sum);  
  }
  private static void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1 | 1]);
  }
  private static void modify(int u,int x,int v){
    if(tr[u].l==x&&tr[u].r==x){
      tr[u].set(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);
    }
  }
  private static 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 left = query(u<<1, l, r);
      Node right = query(u<<1|1, l, r);
      Node res = new Node();
      pushup(res, left, right);
      return res;
    }
  }

  public static void main(String[] args)throws IOException{
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    String[] arr = in.readLine().split(" ");
    int n, m;
    n = Integer.parseInt(arr[0]);
    m = Integer.parseInt(arr[1]);

    String[] cur = in.readLine().split(" ");
    for(int i=1; i<=n; i++) w[i] = Integer.parseInt(cur[i-1]);
    build(1,n,1);

    while(m-->0){
      String[] tmp = in.readLine().split(" ");
      int op = Integer.parseInt(tmp[0]);
      int x = Integer.parseInt(tmp[1]);
      int y = Integer.parseInt(tmp[2]);
      if(op == 1){
        if(x > y){
          int t = x;
          x = y;
          y = t;
        }
        Node s = query(1, x, y);
        System.out.println(s.tmax);
      }else{
        modify(1, x, y);
      }
    }
  }
}

在看答案推信息,从而推出完备性。

升级扩展

(Leetcode)699. 掉落的方块

题目

在二维平面上的 x 轴上,放置着一些方块。
给你一个二维整数数组 positions ,其中 p o s i t i o n s [ i ] = [ l e f t i , s i d e L e n g t h i ] positions[i] = [left_i, sideLength_i] positions[i]=[lefti,sideLengthi]表示:第 i 个方块边长为 s i d e L e n g t h i sideLength_i sideLengthi ,其左侧边与 x 轴上坐标点 l e f t i left_i lefti 对齐。
每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到着陆到 另一个正方形的顶边 或者是 x 轴上 。一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。一旦着陆,它就会固定在原地,无法移动。
在每个方块掉落后,你必须记录目前所有已经落稳的 方块堆叠的最高高度 。
返回一个整数数组 ans ,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。

示例 1:
输入:positions = [[1,2],[2,3],[6,1]]
输出:[2,5,5]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 2 。
第 2 个方块掉落后,最高的堆叠由方块 1 和 2 组成,堆叠的最高高度为 5 。
第 3 个方块掉落后,最高的堆叠仍然由方块 1 和 2 组成,堆叠的最高高度为 5 。
因此,返回 [2, 5, 5] 作为答案。
数据范围

1 ≤ p o s i t i o n s . l e n g t h ≤ 1000 1\leq positions.length\leq 1000 1positions.length1000,
1 ≤ l e f t i ≤ 1 0 8 1\leq left_i \leq 10^8 1lefti108,
1 ≤ s i d e L e n g t h i ≤ 1 0 6 1\leq sideLength _i \leq 10^6 1sideLengthi106,

分析

刚开始做的时候没想到线段树,只想到了合并区间 去走,结果导致一些状态没考虑到,而导致用例过不去,这也就是没有考虑到区间的顺序依赖,从而导致一直出错。

class Solution {
private:
    static const int N = 1e6+10;
    struct Node {
        // l 和 r 分别代表当前区间的左右子节点所在 tr 数组中的下标
        // val 代表当前区间的最大高度,add 为懒标记
        int l, r, val, add;
    }tr[N];
    vector<int> arr;
    void pushup(int u) {
        tr[u].val = max(tr[u << 1].val, tr[u << 1 | 1].val);
    }
    void pushdown(int u) {
        // 定义根节点,左孩子,右孩子
        Node& root = tr[u], &l = tr[u << 1], &r = tr[u << 1 | 1];
        if (tr[u].add) {
            l.add = root.add; l.val = root.add;
            r.add = root.add; r.val = root.add;
            root.add = 0; 
        }
    }
     void build(int u, int l, int r) {
        tr[u] = {l, r, 0, 0};
        if (l == r) return ;
        int mid = (l + r) >> 1;
        build(u << 1, l, mid); 
        build(u << 1 | 1, mid + 1, r);
    }
    void modify(int u, int l, int r, int v) {
        if (l <= tr[u].l && tr[u].r <= r) {
            tr[u].val = v;
            tr[u].add = v;
            return;
        }
        pushdown(u);
        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);
    }
    // 查找l的块
    int find(int x) {
        int l = 0, r = arr.size() - 1, t = -1;
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (arr[mid] >= x) {
                t = mid;
                r = mid - 1;
            } else l = mid + 1;
        }
        return t;
    }


    int query(int u, int l, int r) {
        if (l <= tr[u].l && tr[u].r <= r) return tr[u].val;
        pushdown(u);
        int mid = (tr[u].l + tr[u].r) >> 1;
        int res = 0;
        if (l <= mid) res = query(u << 1, l, r);
        if (r > mid) res = max(res, query(u << 1 | 1, l, r));
        return res;
    }

public:
    vector<int> fallingSquares(vector<vector<int>>& positions) {
        vector<int> res;
        // arr是辅助的,主要是用来计算节点的数目。
        for (auto &p : positions) {
            arr.push_back(p[0]);
            arr.push_back(p[0] + p[1] - 1);
        }
        sort(arr.begin(), arr.end());
        arr.erase(unique(arr.begin(), arr.end()), arr.end());
        build(1, 0, arr.size() - 1);

        for(vector<int> p : positions){
            int l = p[0], h = p[1], v = query(1, find(l), find(l + h - 1));
            modify(1, find(l), find(l + h - 1), v + h);
            res.push_back(tr[1].val);
        }
        return res;
        
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值