基础概念
可以参考线段树,来大概了解其的内容,感觉他写的好好。
在这个里面,我们大概了解了什么是线段树,那么我们为什么要用线段树,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之间。可以对这列数进行两种操作:
- 添加操作:向序列后添加一个数,序列长度变成 n+1;
- 询问操作:询问这个序列中最后 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 1≤m≤2×105,
1 ≤ p ≤ 2 × 1 0 9 1\leq p \leq 2×10^9 1≤p≤2×109,
1 ≤ t < p 1\leq t \lt p 1≤t<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]} maxx≤l≤r≤y∑i=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 N≤500000,M≤100000,
− 1000 ≤ A [ i ] ≤ 1000 −1000 \leq A[i] \leq 1000 −1000≤A[i]≤1000
分析
首先题目要求即为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 1≤positions.length≤1000,
1 ≤ l e f t i ≤ 1 0 8 1\leq left_i \leq 10^8 1≤lefti≤108,
1 ≤ s i d e L e n g t h i ≤ 1 0 6 1\leq sideLength _i \leq 10^6 1≤sideLengthi≤106,
分析
刚开始做的时候没想到
线段树
,只想到了合并区间
去走,结果导致一些状态没考虑到,而导致用例过不去,这也就是没有考虑到区间的顺序依赖,从而导致一直出错。
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;
}
};