第五讲 树状数组与线段树
创始人
2025-05-30 15:18:37
0

文章目录

    • 1. 动态求连续区间和(线段树)
    • 2.数星星(树状数组)
    • 3.数列区间最大值(线段树维护区间最大值)
    • 4.小朋友排队(归并排序/树状数组/冒泡)
    • 5.油漆面积(线段树/扫描线❗)
    • 6.三体攻击(二分+前缀和+三维差分❗)
    • 7.螺旋折线(推规律)❗
    • 8.差分(差分)
    • 9.差分矩阵(二维差分)

1. 动态求连续区间和(线段树)

在这里插入图片描述
💡💡💡思路:
典型的线段树点修改与区间和


public class Main{static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));static int N = (int) (1e5 + 10);static int[] a = new int[N];static node[] t = new node[N*4];static int n = 0, m = 0;public static void main(String[] args) throws Exception{init();String[]nm = br.readLine().split(" ");n = Integer.parseInt(nm[0]);m = Integer.parseInt(nm[1]);String[] aa = br.readLine().split(" ");for(int i = 1; i <= n; i++) {a[i] = Integer.parseInt(aa[i - 1]);}build(1,1,n);for(int i = 1; i <= m; i++) {String[]kab = br.readLine().split(" ");int k = Integer.parseInt(kab[0]);int a = Integer.parseInt(kab[1]);int b = Integer.parseInt(kab[2]);if(k == 0) {System.out.println(query(1,a,b));}else {update(1,a,a,b);}}}private static void update(int p, int l, int r, int d) {if(t[p].l >= l && t[p].r <= r) {t[p].sum += (t[p].r - t[p].l + 1)*d;t[p].add += d;return;}spread(p);int mid = (t[p].l + t[p].r) >> 1;if(l <= mid) {update(p*2, l, r,d);}if(r > mid) {update(p*2+1, l, r,d);}t[p].sum = t[p*2].sum + t[p*2 + 1].sum;}private static void spread(int p) {if(t[p].add == 0) {return;}t[p*2].sum += t[p].add*(t[p*2].r - t[p*2].l + 1);t[p*2 + 1].sum += t[p].add*(t[p*2 + 1].r - t[p*2 + 1].l + 1);t[p*2].add += t[p].add;t[p*2+1].add += t[p].add;t[p].add = 0;}private static long query(int p, int l, int r) {if(t[p].l >= l && t[p].r <= r) {return t[p].sum;}spread(p);long val = 0;int mid = (t[p].l + t[p].r) >> 1;if(l <= mid) {val += query(p*2, l, r);}if(r > mid) {val += query(p*2+1, l, r);}return val;}private static void init() {for(int i = 0; i < N*4; i++) {t[i] = new node();}}private static void build(int p, int l, int r) {t[p].l = l;t[p].r = r;if(l == r) {t[p].sum = a[l];return;}int mid = (t[p].l + t[p].r) >> 1;build(p*2,l,mid);build(p*2+1, mid + 1, r);t[p].sum = t[p*2].sum + t[p*2+1].sum;}
}
class node{int l,r;int add,sum;
}

2.数星星(树状数组)

3.数列区间最大值(线段树维护区间最大值)

在这里插入图片描述

public class Main{static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));static int N = (int) (1e5 + 10);static int[] a = new int[N];static node[] t = new node[N*4];static int n = 0, m = 0;public static void main(String[] args) throws Exception{init();String[]nm = br.readLine().split(" ");n = Integer.parseInt(nm[0]);m = Integer.parseInt(nm[1]);String[] aa = br.readLine().split(" ");for(int i = 1; i <= n; i++) {a[i] = Integer.parseInt(aa[i - 1]);}build(1,1,n);for(int i = 1; i <= m; i++) {String[]ab = br.readLine().split(" ");int a = Integer.parseInt(ab[0]);int b = Integer.parseInt(ab[1]);out.println(query(1,a,b));}out.flush();}private static long query(int p, int l, int r) {if(t[p].l >= l && t[p].r <= r) {return t[p].maxn;}long val = -Integer.MAX_VALUE; //注意初始化int mid = (t[p].l + t[p].r) >> 1;if(l <= mid) {val =Math.max(val, query(p*2, l, r));}if(r > mid) {val = Math.max(val,query(p*2+1, l, r));}return val;}private static void init() {for(int i = 0; i < N*4; i++) {t[i] = new node();}}private static void build(int p, int l, int r) {t[p].l = l;t[p].r = r;if(l == r) {t[p].maxn = a[l];return;}int mid = (t[p].l + t[p].r) >> 1;build(p*2,l,mid);build(p*2+1, mid + 1, r);t[p].maxn = Math.max(t[p*2].maxn , t[p*2+1].maxn);}
}
class node{int l,r;int maxn;
}

4.小朋友排队(归并排序/树状数组/冒泡)

在这里插入图片描述

思路:其实就是求每个人前面比它大的,后面比它小的,显然是求逆序对
法1:冒泡排序

显然求解过程符合冒泡排序的原理,每次只允许交换相邻的两个元素,但是这种方法肯定是会T的

#include 
using namespace std;
#define int long long
const int N = 100010;
struct node {int val;int k;
}a[N];
signed main() {int n;cin >> n;for (int i = 1; i <= n; i ++) {cin >> a[i].val;a[i].k = 0;}for (int i = 1; i <= n - 1; i ++) {for (int j = 1; j <= n - 1; j ++) {if (a[j].val > a[j+1].val) {a[j].k ++;a[j+1].k ++;node t = a[j];a[j] = a[j+1];a[j+1] = t;}}}int sum = 0;for (int i = 1; i <= n; i ++) {// cout << a[i].k << ' ';sum += a[i].k * (a[i].k + 1) / 2;}cout << sum;;
}

法2:归并排序
同平时的归并排序求逆序对有所不同,我们需要求前面比它大的,后面比它小的,因此需要进行两次归并排序,升序排序求前面比它大的个数,降序排序求后面比他小的。
需要注意的是java中深拷贝与浅拷贝(深拷贝中基本数据类型是值传递,但是引用类型的话就是指向同一地址空间,所以在对b进行赋值的时候,不能直接将a赋值给b,会导致他俩的操作互相影响)

#include 
using namespace std;
#define int long long
const int N = 100010;
struct node {int val;int k;
}a[N];
signed main() {int n;cin >> n;for (int i = 1; i <= n; i ++) {cin >> a[i].val;a[i].k = 0;}for (int i = 1; i <= n - 1; i ++) {for (int j = 1; j <= n - 1; j ++) {if (a[j].val > a[j+1].val) {a[j].k ++;a[j+1].k ++;node t = a[j];a[j] = a[j+1];a[j+1] = t;}}}int sum = 0;for (int i = 1; i <= n; i ++) {// cout << a[i].k << ' ';sum += a[i].k * (a[i].k + 1) / 2;}cout << sum;;
}

5.油漆面积(线段树/扫描线❗)

在这里插入图片描述
**加粗样式
法1:暴力枚举标记(TLE)
需要注意遍历循环的下标范围
在这里插入图片描述
比如上图说(1,1)和(4,4)为矩形两个对角点,那么我们可以覆盖的范围显然是绿色部分(所以遍历的时候两边只能取到一边,取哪边都是可以的)

for(int x = x1 + 1; x <= x2; x++) {for(int y = y1 + 1; y <= y2; y++) {if(!vis[x][y]) {vis[x][y] = true;sum++;}}}

public class Main{static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));static int N = (int) (1e4 + 10);static boolean vis[][] = new boolean[N][N];static int n = 0, m = 0;static long sum = 0;public static void main(String[] args) throws Exception{n = Integer.parseInt(br.readLine());for(int i = 1; i <= n; i++) {String[] xy = br.readLine().split(" ");int x1 = Integer.parseInt(xy[0]);int y1= Integer.parseInt(xy[1]);int x2 = Integer.parseInt(xy[2]);int y2 = Integer.parseInt(xy[3]);for(int x = x1; x < x2; x++) {  //此处不能取等号,否则会导致多算for(int y = y1; y < y2; y++) {  //此处同上if(!vis[x][y]) {vis[x][y] = true;sum++;}}}}out.println(sum);out.flush();}}

法2:线段树+扫面线(有点难😢)

在这里插入图片描述

有点难

留坑

6.三体攻击(二分+前缀和+三维差分❗)

在这里插入图片描述

在这里插入图片描述
感觉看起来像差分,但是并不会,哈哈哈
没想到还有三维差分(✪ ω ✪)
具有单调性,二分答案判定是否可行

s(x,y,z)
b(x,y.x)
首先压缩掉z轴
s(x.y,z) = s(x - 1,y,z) + s(x,y-1,z)-s(x-1,y-1,z)+b(x,y,z)+s(x,y,z-1)-s(x-1,y,z-1)-s(x,y-1,z-1)+s(x-1,y-1,z-1)
在这里插入图片描述

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
public class Main 
{static final int N = 2000010;static long s[] = new long[N], b[] = new long[N],bp[] = new long[N];static int op[][] = new int[N / 2][7]; static int d[][] = {{0, 0, 0, 1},{0, 0, 1, -1},{0, 1, 0, -1},{0, 1, 1, 1},{1, 0, 0, -1},{1, 0, 1, 1},{1, 1, 0, 1},{1, 1, 1, -1},};static int A, B, C, m;public static void main(String[] args) throws Exception{BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String str1[] = br.readLine().split(" ");A = Integer.parseInt(str1[0]);B = Integer.parseInt(str1[1]);C = Integer.parseInt(str1[2]);m = Integer.parseInt(str1[3]);String str2[] = br.readLine().split(" ");for(int i = 1; i <= A; i ++)for(int j = 1; j <= B; j ++)for(int k = 1; k <= C; k ++)s[get(i, j, k)] = Long.parseLong(str2[get(i - 1, j - 1, k - 1)]);//构造差分矩阵b[]for(int i = 1; i <= A; i ++)for(int j = 1; j <= B; j ++)for(int k = 1; k <= C; k ++)for(int u = 0; u < 8; u ++){int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];b[get(i, j, k)] += s[get(x, y, z)] * t;}//操作for(int i = 1; i <= m; i ++){String str3[] = br.readLine().split(" ");for(int j = 0; j < 7; j ++)op[i][j] = Integer.parseInt(str3[j]);}//二分攻击次数int l = 1, r = m;int res = -1;while(l <= r) {int mid = (l + r) >> 1;if(check(mid)) {res = mid;r = mid - 1;}else {l = mid + 1;}}System.out.println(res);}//三维坐标(i, j, k)映射成一维坐标(i * B + j) * C + kpublic static int get(int i, int j, int k){return (i * B + j) * C + k;}//二分答案public static boolean check(int mid){//拷贝一份for(int i = 0; i < N; i ++)bp[i] = b[i];for(int i = 1; i <= mid; i ++){int x1 = op[i][0], x2 = op[i][1];int y1 = op[i][2], y2 = op[i][3];int z1 = op[i][4], z2 = op[i][5];int h = op[i][6];bp[get(    x1,      y1,      z1)] -= h;bp[get(    x1,      y1,  z2 + 1)] += h;bp[get(    x1,  y2 + 1,      z1)] += h;bp[get(    x1,  y2 + 1,  z2 + 1)] -= h;bp[get(x2 + 1,      y1,      z1)] += h;bp[get(x2 + 1,      y1,  z2 + 1)] -= h;bp[get(x2 + 1,  y2 + 1,      z1)] -= h;bp[get(x2 + 1,  y2 + 1,  z2 + 1)] += h;}Arrays.fill(s, 0);for(int i = 1; i <= A; i ++)for(int j = 1; j <= B; j ++)for(int k = 1; k <= C; k ++){s[get(i, j, k)] = bp[get(i, j, k)];for(int u = 1; u < 8; u ++){int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];s[get(i, j, k)] -= s[get(x, y, z)] * t;}if(s[get(i, j, k)] < 0)return true;}return false;}
}

7.螺旋折线(推规律)❗

在这里插入图片描述

在这里插入图片描述

看大佬的博客,真的很奇妙博客来源
找规律发现每层的右上角所需步数为4k*k,我们可以先通过坐标推出所在的层数,因为我们是顺时针转的,所以当点在
该层的左或上的时候,减去该点到右上角的曼哈顿距离,如果在该层的有或下加上该点到右上角的曼哈顿距离。
注意数据范围

public class Main{static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));static int N = (int) (2e6 + 10);static int n = 0, m = 0;public static void main(String[] args) throws Exception{String[] xy = br.readLine().split(" ");long x = Long.parseLong(xy[0]);long y = Long.parseLong(xy[1]);long k = Math.max(Math.abs(x),Math.abs(y));long base = 4 * k * k;if(x >= y) {System.out.println(base + Math.abs(x - k) + Math.abs(y - k));}else {System.out.println(base - Math.abs(x - k) - Math.abs(y - k));}}
}

8.差分(差分)

在这里插入图片描述
在这里插入图片描述
差分模板


public class Main{static BufferedReader br = new BufferedReader(new InputStreamReader((System.in)));static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));static int N = (int)(1e5 + 100);static int[] a = new int[N];static int[] d = new int[N];static int n = 0, m = 0;public static void main(String[] args) throws Exception{String[]nm = br.readLine().split(" ");n = Integer.parseInt(nm[0]);m = Integer.parseInt(nm[1]);String[] aa = br.readLine().split(" ");for(int i = 1;i <= n; i++){a[i] = Integer.parseInt(aa[i - 1]);d[i] = a[i] - a[i - 1];}for(int i = 1; i <= m; i++){String[] lrc = br.readLine().split(" ");int l = Integer.parseInt(lrc[0]);int r = Integer.parseInt(lrc[1]);int c = Integer.parseInt(lrc[2]);d[l]+=c; d[r+1]-=c;}for(int i = 1; i <= n; i++){a[i] = d[i] + a[i - 1];}for(int i = 1; i <= n; i++){System.out.print(a[i] + " ");}}
}

9.差分矩阵(二维差分)

在这里插入图片描述

在这里插入图片描述
画个图,类比一下一维差分就好啦

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class Main{static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));static int N = (int) (1e3 + 10);static int[][] d = new int[N][N];static long [][] sum = new long[N][N];static int n = 0, m = 0;public static void main(String[] args) throws Exception{String[] nm = br.readLine().split(" ");n = Integer.parseInt(nm[0]);m = Integer.parseInt(nm[1]);int q = Integer.parseInt(nm[2]);for(int i = 1; i <= n; i++) {String[] aa = br.readLine().split(" ");for(int j = 1; j <= m; j++) {sum[i][j] = Integer.parseInt(aa[j - 1]);insert(i,j,i,j,sum[i][j]);}}for(int i = 1; i <= q; i++) {String[] ops = br.readLine().split(" ");int x1 = Integer.parseInt(ops[0]);int y1 = Integer.parseInt(ops[1]);int x2 = Integer.parseInt(ops[2]);int y2 = Integer.parseInt(ops[3]);long c = Long.parseLong(ops[4]);insert(x1, y1, x2, y2, c);}for(int i = 1; i <= n; i++) {for(int j = 1; j <= m; j++) {sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + d[i][j];}}for(int i = 1; i <= n; i++) {for(int j = 1; j <= m; j++) {out.print(sum[i][j] + " ");}out.println();}out.flush();}private static void insert(int x1, int y1, int x2, int y2, long c) {d[x1][y1] += c;d[x1][y2 +1] -= c;d[x2 + 1][y1] -= c;d[x2 + 1][y2 + 1] += c; }
}

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...