第四章 No.1树状数组的原理与使用

news2024/9/29 7:28:28

文章目录

    • 应用问题
    • 原理
    • 树状数组练习题
      • 241. 楼兰图腾
      • 242. 一个简单的整数问题
      • 243. 一个简单的整数问题2
      • 244. 谜一样的牛

线段树的反面:树状数组原理复杂,实现简单

应用问题

支持两个操作:快速求前缀和任意地修改某个数,时间复杂度为 O ( l o g n ) O(logn) O(logn)
用前缀和数组,求前缀和的复杂度为 O ( 1 ) O(1) O(1),但是任意修改某个数的复杂度为 O ( n ) O(n) O(n)
用数组,求前缀和的复杂度为 O ( n ) O(n) O(n),修改某个数的时间复杂度为 O ( 1 ) O(1) O(1)

而使用树状数组,可以以适中的时间复杂度解决以上问题


原理

原理就是二进制表示,一个整数x,将其二进制表示后,可以直观地发现该数可以表示成多个2的幂相加
比如10110可以表示成: 2 1 + 2 2 + 2 4 2^1+2^2+2^4 21+22+24
在最坏情况下,一个32位整数n需要将32个2的幂相加,即 O ( l o g n ) O(logn) O(logn)

分析一下:将1~x整个区间划分后,每个子区间是怎样的?
image.png
每一个区间长度都为2的幂
第一个区间为 ( x − 2 i 1 , x ] (x-2^{i_1}, x] (x2i1,x],长度为 2 i 1 2^{i_1} 2i1
第二个区间为 ( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] (x-2^{i_1}-2^{i_2}, x-2^{i_1}] (x2i12i2,x2i1],长度为 2 i 2 2^{i_2} 2i2

最后一个区间为 ( 0 , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] (0, x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}] (0,x2i12i2...2ik1],长度为 2 i k 2^{i_k} 2ik
其中k为x中1的个数

可以发现,区间 ( l , r ] (l, r] (l,r]可以表示成 [ r − l o w b i t ( r ) + 1 , r ] [r-lowbit(r)+1, r] [rlowbit(r)+1,r]
因为区间长度一定是r的最后一位1对应的幂,即lowbit(r)

假设数组长度为n,对于1~n之间的所有数,将其作为区间 [ r − l o w b i t ( r ) + 1 , r ] [r-lowbit(r)+1, r] [rlowbit(r)+1,r]的右端点,则这些区间将有重复地覆盖1~n整个区间
长度为16的数组的划分:
image.png

思考每个区间之间的关系,当前区间可以由哪些区间得到?则有:
image.png

将每段区间看成节点,再看上面这张图
连接了存在关系的区间,那么所有区间就构成了一颗树。这便是树状数组名字的由来,十分的形象,思考父子节点之间的关系

对于 C 16 C_{16} C16这个区间,它由 ( a 16 , C 15 , C 14 , C 12 , C 8 ) (a_{16}, C_{15}, C_{14}, C_{12}, C_{8}) (a16,C15,C14,C12,C8)组成,再举例几个区间后
可以看出规律:区间 C n C_n Cn由区间 ( a n , C . . . ) (a_n, C...) (an,C...)组成,其中除了 a n a_n an,剩下区间的下标k加上lowbit(k)后的结果为16
15:01111 + 1 = 10000,01111 - 1 = 01110
14:01110 + 10 = 10000,01110 - 10 = 01100
12:01100 + 100 = 10000,01100 - 100 = 01000
8:01000 + 1000 = 10000,01000 - 1000 = 00000
观察这些下标k的规律,每次下标变小都是减去了lowbit(k),当下标为0,说明当前节点没有子节点了

image.png

通过以上推导,可以发现子节点下标到父节点下标的规律:
假设当前区间为 C x C_x Cx,那么 C x + l o w b i t ( x ) C_{x+lowbit(x)} Cx+lowbit(x)是其父节点, C x + l o w b i t ( l o w b i t ( x ) ) C_{x+lowbit(lowbit(x))} Cx+lowbit(lowbit(x))是其祖父节点…直到下标超过n。此时作为 C x C_x Cx的祖先节点不存在
为什么要知道子节点到父节点的关系?由于 C x C_x Cx维护着数组中的某段区间和,并且这些区间之间存在着重叠,修改数组中的任意一个数后,必定会向上影响其的父节点的区间和,此时只能通过子节点不断地更新到根节点,才能维护正确的数据。所以知道子节点到父节点的关系是有必要的

总结一下:
子节点到父节点的推导:假设子节点在数组中的下标为x,需要不断地对子节点的下标加上lowbit(x),获取其父节点的下标直到下标超过数组长度的最大值
由此就能实现修改任意位置的成员

// 对原数组x下标位置上的数+c
void add(int x, int c)
{
	for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

其中tr数组是维护的树状数组

第二个问题:如何获取前缀和?
image.png
还是这张图,假设要获取前16个数的和,直接返回树状数组的 C 16 C_{16} C16即可,也就是树状数组的第16个成员
假设要获取前14个数的和,需要返回 C 14 + C 12 + C 8 C_{14}+C_{12}+C_8 C14+C12+C8,将它们的下标转换成二进制
001110
001100
001000
000000
可以发现,在整个数为0之前,二进制表示的最后一个1不断被减去
经过归纳,求前i个数的和时,需要对树状数组中的数进行累加,这些数的下标从i开始,不断地减去最后一位1,直到i为0
注意不要先减去1再不断减去lowbit(k),那是求节点的子节点方式
直接减去lowbit(k)是求前缀和的方式

int sum(int x)
{
	int res = 0;
	for (int i = x; i; i -= lowbit(i)) res += tr[i];
	return res;
}

如何建立数组数组?
建立树状数组的方式有两种,最简单的方式是直接调用add将需要修改的数添加(一开始树状数组的所有成员为0),时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

此外,还能直接根据定义建数组,先求出原数组a[n]的前缀和数组s[n]
树状数组中,区间 ( r − l o w b i t ( r ) + 1 , r ) (r-lowbit(r)+1, r) (rlowbit(r)+1,r)的值为s[r] - s[r - lowbit[r]],此时直接修改原数组即可
由于不用调用add,所以时间复杂度为 O ( n ) O(n) O(n),但该做法额外维护了一个前缀和数组


树状数组练习题

241. 楼兰图腾

241. 楼兰图腾 - AcWing题库
image.png

划分所有的纵坐标成k个集合,每个集合表示纵坐标为 y k y_k yk时,能构成的v的数量
( x , y k ) (x, y_k) (x,yk)能构成的v的数量:在横坐标小于x的坐标中,纵坐标大于 y k y_k yk的坐标数量乘以横坐标大于x的坐标中,纵坐标大于 y k y_k yk的坐标数量
( x , y k ) (x, y_k) (x,yk)能构成的的数量:在横坐标小于x的坐标中,纵坐标小于 y k y_k yk的坐标数量乘以横坐标大于x的坐标中,纵坐标小于 y k y_k yk的坐标数量

树状数组存储纵坐标小于等于当前下标的坐标数量,比如tr[i]表示纵坐标小于等于i的坐标数量
将所有纵坐标按照横坐标的升序顺序存储到数组a中,遍历a[i]时,需要求出纵坐标小于a[i]以及纵坐标大于a[i]的坐标数量,分别用l数组和g数组存储。这是横坐标小于x且纵坐标大于或小于a[i]的坐标数量,也就是前缀和
a[i]的前缀和求完后,需要将tr[a[i]]加1,表示纵坐标小于等于a[i]的坐标数量+1
前缀和求完后再求后缀和,便能得到答案

tr[i]存储纵坐标小于等于a[i]的坐标数量,如何求纵坐标小于a[i]的坐标数量?tr[a[i]-1]即可
如何求纵坐标大于a[i]的坐标数量,tr[max] - tr[a[i]]即可,max表示所有纵坐标中的最大值

#include <iostream>
#include <cstring>
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
int a[N], l[N], g[N], tr[N];
int n;

int lowbit(int x)
{
    return x & (~x + 1);
}

int sum(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

void add(int x, int c)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++ i ) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; ++ i )
    {
        int y = a[i];
        g[i] = sum(n) - sum(y);
        l[i] = sum(y - 1);
        add(y, 1);
    }
    
    LL res1 = 0, res2 = 0;
    memset(tr, 0, sizeof(tr));
    for (int i = n; i >= 1; -- i )
    {
        int y = a[i];
        res1 += g[i] * (LL)(sum(n) - sum(y));
        res2 += l[i] * (LL)sum(y - 1);
        add(y, 1);
    }
    
    printf("%lld %lld\n", res1, res2);
    
    return 0;
}

242. 一个简单的整数问题

242. 一个简单的整数问题 - AcWing题库
image.png

将某段区间中的数加上c,而不是修改成c,使用差分数组可以完美解决此问题
查询原数组中某个数的值,使用差分数组需用 O ( n ) O(n) O(n),总的复杂度为 O ( n m ) O(nm) O(nm),可能会超时,所以考虑如何优化

通过差分数组求原数组的某个数其实是一个前缀和操作,要优化 O ( n ) O(n) O(n)的前缀和可以考虑树状数组。自然地,树状数组存储的是原数组的差分信息,1. 将区间 [ l , r ] [l, r] [l,r]加上c时,只用修改 l l l r + 1 r+1 r+1位置上的数即可,复杂度为 O ( l o g n ) O(logn) O(logn) 2. 求原数组的某个数时,使用树状数组的sum操作即可,时间复杂度为 O ( l o g n ) O(logn) O(logn)
总的时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn),不会超时

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
int a[N], tr[N];
int n, m;

int lowbit(int x)
{
    return x & (~x + 1);
}

void add(int x, int c)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

LL sum(int x)
{
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i )  scanf("%d", &a[i]);

    for (int i = 1; i <= n; ++ i ) tr[i] = a[i] - a[i - lowbit(i)];
    
    int l, r, d, x; char op[2];
    while (m -- )
    {
        scanf("%s", op);
        if (op[0] == 'C') 
        {
            scanf("%d%d%d", &l, &r, &d);
            add(l, d); add(r + 1, -d);
        }
        else
        {
            scanf("%d", &x);
            printf("%lld\n", sum(x));
        }
    }
    return 0;
}

debug:add(l, d); add(r + 1, -d),后面那个add习惯地写成了add(r + 1, d)
以及可能存在的爆int问题,一直都没有特别注意


243. 一个简单的整数问题2

243. 一个简单的整数问题2 - AcWing题库
image.png

需要实现两个操作:1. 对区间中的所有数加上一个数 2. 求区间和
与上题思路类似,保存差分信息以实现第一个操作
求区间和时,思考如何求原数组中的某个数 a i a_i ai?这需要对差分数组前缀求和
那么如何求原数组中的区间和呢?分别求出区间中的每个数吗?就算对前缀和求解过程进行优化,也要 O ( n ) O(n) O(n)

思考如何优化对n个数求前缀和?如下图,列出 a [ 1 ] a[1] a[1] a [ x ] a[x] a[x]中的每个数需要的差分信息,补全这些差分信息(图中红蓝部分
image.png
可以发现区间 ( 1 , x ) (1, x) (1,x)的和就是蓝色部分,等于所有的和减去红色的和
( b 1 + b 2 + . . . + b x ) ( x + 1 ) − ( 1 b 1 + 2 b 2 + . . . + x b x ) (b_1+b_2+...+b_x)(x+1)-(1b_1+2b_2+...+xb_x) (b1+b2+...+bx)(x+1)(1b1+2b2+...+xbx)
就是 b i b_i bi的前缀和乘以 ( x + 1 ) (x+1) (x+1)再减去 i b i ib_i ibi的前缀和
b i b_i bi的前缀和可以通过树状数组tr1维护, i b i ib_i ibi的前缀和可以通过树状数组tr2维护
对于题目需要的两个操作,维护这两个数组可以实现第二个操作,同时tr1数组能实现第一个操作

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;

LL tr1[N], tr2[N];
int a[N];
int n, m;

int lowbit(int x)
{
    return x & (~x + 1);
}

void add(LL tr[], int x, LL c)
{
    for (int i = x; i <= n; i += lowbit(i) ) tr[i] += c;
}

LL sum(LL tr[], int x)
{
    LL res = 0;
    for (int i = x; i; i-= lowbit(i) ) res += tr[i];
    return res;
}

LL prefix_sum(int x)
{
    return (x + 1) * sum(tr1, x) - sum(tr2, x);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d", &a[i]);
    for (int i = 1; i <= n; ++ i ) 
    {
        LL b = a[i] - a[i - 1];
        add(tr1, i, b);
        add(tr2, i, b * i);
    }
    
    char op[2]; LL l, r, d;
    while (m -- )
    {
        scanf("%s%lld%lld", op, &l, &r);
        if (op[0] == 'C')
        {
            scanf("%lld", &d);
            add(tr1, l, d), add(tr1, r + 1, -d);
            add(tr2, l, l * d), add(tr2, r + 1, (r + 1) * -d);
        }
        else
        {
            printf("%lld\n", prefix_sum(r) - prefix_sum(l - 1));
        }
    }
    
    return 0;
}

debug:总是习惯LL用%d读取


244. 谜一样的牛

244. 谜一样的牛 - AcWing题库
image.png

从最后一个数开始往前推,若 a n a_n an k k k,说明前面有k头身高低于第n头牛的牛,此时 a n a_n an为所有身高中第 k + 1 k+1 k+1小的数,将该身高删除,因为每头牛的身高不同
a i a_i ai k k k,说明 a i a_i ai为剩下的身高中第 k + 1 k+1 k+1小的数

综上,需要实现两个操作:1. 删除某个身高 2. 计算身高中第 k k k小的数
除了平衡树,树状数组也能实现这两个操作
因为每头牛的身高不同,所以1~n中的每个数只能使用一次,将数组 b [ n ] b[n] b[n]的所有成员初始化成1,表示每个身高都没有使用过。一旦使用了某个身高,对应位置上的成员-1,由此可以实现第一个操作
用树状数组维护 b [ n ] b[n] b[n]数组的前缀和,如何计算数组中第k小的身高?若sum(x) = i说明小于等于x的身高中,有i个升高没有被使用
只要找到第一个满足sum(x) = k的x即可,此时的x就是剩下身高中第k小的身高,这个用二分可以实现

#include <iostream>
using namespace std;

const int N = 1e5 + 10;
int n, a[N], tr[N], ans[N];

int lowbit(int x)
{
    return x & (~x + 1);
}

void add(int x, int c)
{
    for (int i = x; i <= n; i += lowbit(i) ) tr[i] += c; 
}

int sum(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int find(int x)
{
    int l = 1, r = n;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (sum(mid) >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++ i ) 
    {
        if (i > 1) scanf("%d", &a[i]);
        add(i, 1);
    }
    
    for (int i = n; i; -- i ) 
    {
        ans[i] = find(a[i] + 1); // sum(mid) <= x
        add(ans[i], -1);
    }
    
    for (int i = 1; i <= n; ++ i ) printf("%d\n", ans[i]);
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/808345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

520 · 一致性哈希 II

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a; class Solution{private:int n;const int mVirtualNodeCount;map<int, int> mVirtualNodeToMachineIdMap;set<int> mVirtualNodeSet;public:Solution(int n…

SpringBoot的三层架构以及IOCDI

目录 一、IOC&DI入门 二、三层架构 数据库访问层 业务逻辑层 控制层 一、IOC&DI入门 在软件开发中&#xff0c;IOC&#xff08;Inversion of Control&#xff09;和DI&#xff08;Dependency Injection&#xff09;是密切相关的概念。 IOC&#xff08;控制反转&a…

CAN学习笔记2:CAN简介

CAN 1 概述 CAN(Controller Area Network),是 ISO 国际标准化的串行通信协议,目的是适应汽车“减少线束的数量”、“通过多个网络进行大量数据的高速传输”的需求。 低速 CAN(ISO11519)通信速率 10~125Kbps&#xff0c;总线长度可达 1000米. 高速 CAN(ISO11898)通信速率 125~…

cnn卷积神经网络(基础)

convolutional neural networks 特征提取&#xff08;卷积、下采样&#xff09;->分类器 &#xff08;全连接&#xff09; 卷积过程 依次进行数乘 &#xff08;每个相同位置上的数字相乘再加和&#xff09; 左右数乘矩阵channel数量要一样&#xff0c;输出得到一个通道 卷…

吴师傅教你几招极速清理C盘,高能操作绝不让你失望!

电脑使用久了&#xff0c;C盘堆积的垃圾过多&#xff1b;每天上网会给电脑带来很多临时文件&#xff0c;这些垃圾文件不清理掉时间久了就会影响到电脑的运行速度&#xff1b;也会导致C盘变红&#xff0c;空间不足。那么&#xff0c;电脑C盘满了如何清理呢&#xff1f;教你几招极…

SpringBoot 如何进行 统一异常处理

在Spring Boot中&#xff0c;可以通过自定义异常处理器来实现统一异常处理。异常处理器能够捕获应用程序中抛出的各种异常&#xff0c;并提供相应的错误处理和响应。 Spring Boot提供了ControllerAdvice注解&#xff0c;它可以将一个类标记为全局异常处理器。全局异常处理器能…

NICE-SLAM: Neural Implicit Scalable Encoding for SLAM论文阅读

论文信息 标题&#xff1a;NICE-SLAM: Neural Implicit Scalable Encoding for SLAM 作者&#xff1a;Zihan Zhu&#xff0c; Songyou Peng&#xff0c;Viktor Larsson — Zhejiang University 来源&#xff1a;CVPR 代码&#xff1a;https://pengsongyou.github.io/nice-slam…

ARM单片机中断处理过程解析

前言 中断&#xff0c;在单片机开发中再常见不过了。当然对于中断的原理和执行流程都了然于胸&#xff0c;那么对于ARM单片机中断的具体处理行为&#xff0c;你真的搞清楚了吗&#xff1f; 今天来简单聊一聊&#xff0c;ARM单片机中断处理过程中的具体行为是什么样的&#xf…

spring5源码篇(13)——spring mvc无xml整合tomcat与父子容器的启动

spring-framework 版本&#xff1a;v5.3.19 文章目录 整合步骤实现原理ServletContainerInitializer与WebApplicationInitializer父容器的启动子容器的启动 相关面试题 整合步骤 试想这么一个场景。只用 spring mvc&#xff08;确切来说是spring-framework&#xff09;&#x…

Windows环境下安装及部署Nginx

一、安装Nginx教程 1、官网下载地址&#xff1a;https://nginx.org/en/download.html 2、下载教程&#xff1a;选择Stable version版本下载到本地 3、下载完成后&#xff0c;解压放入本地非中文的文件夹中&#xff1a; 4、启动nginx&#xff1a;双击nginx.exe&#xff0c;若双击…

Vue 3:玩一下web前端技术(五)

前言 本章内容为VUE语法的简单学习与相关语法讨论。 上一篇文章地址&#xff1a; Vue 3&#xff1a;玩一下web前端技术&#xff08;四&#xff09;_Lion King的博客-CSDN博客 下一篇文章地址&#xff1a; Vue 3&#xff1a;玩一下web前端技术&#xff08;六&#xff09;_L…

算法与数据结构(四)--排序算法

一.冒泡排序 原理图&#xff1a; 实现代码&#xff1a; /* 冒泡排序或者是沉底排序 *//* int arr[]: 排序目标数组,这里元素类型以整型为例; int len: 元素个数 */ void bubbleSort (elemType arr[], int len) {//为什么外循环小于len-1次&#xff1f;//考虑临界情况&#xf…

自动驾驶感知系统-全球卫星定位系统

卫星定位系统 车辆定位是让无人驾驶汽车获取自身确切位置的技术&#xff0c;在自动驾驶技术中定位担负着相当重要的职责。车辆自身定位信息获取的方式多样&#xff0c;涉及多种传感器类型与相关技术。自动驾驶汽车能够持续安全可靠运行的一个关键前提是车辆的定位系统必须实时…

为什么你的独立站有流量没转化?如何做诊断检查?

新店的创业初期&#xff0c;即使网站有流量&#xff0c;但是销售额为零的情况也常有发生。如果你确定流量是高质量的&#xff0c;寻找阻止潜在客户购买的具体因素可能会感到困难重重。 从“立即购买”按钮的色彩选择这样的细节&#xff0c;到构建品牌故事这样的大计划&#xf…

开发一个RISC-V上的操作系统(四)—— 内存管理

目录 往期文章传送门 一、内存管理简介 二、Linker Script 链接脚本 三、动态分配内存 四、测试 往期文章传送门 开发一个RISC-V上的操作系统&#xff08;一&#xff09;—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统&#xff08;二…

springboot创建并配置环境(三) - 配置扩展属性(上集)

文章目录 一、介绍二、配置文件application.yml 一、介绍 在上一篇文章&#xff1a;springboot创建并配置环境(二) - 配置基础环境中&#xff0c;我们介绍了springboot如何配置基础环境变量。本篇文章讨论如何处理配置文件。即来自不同位置的配置属性&#xff0c;如&#xff1…

chatglm2外挂知识库问答的简单实现

一、背景 大语言模型应用未来一定是开发热点&#xff0c;现在一个比较成功的应用是外挂知识库。相比chatgpt这个知识库比较庞大&#xff0c;效果比较好的接口。外挂知识库大模型的方式可以在不损失太多效果的条件下获得数据安全。 二、原理 现在比较流行的一个方案是langcha…

OpenLayers入门,OpenLayers使用瓦片加载事件实现瓦片加载进度条,进度条根据瓦片加载数量自动更新进度,加载完毕后隐藏进度条

专栏目录: OpenLayers入门教程汇总目录 前言 本章主要讲解OpenLayers如何使用瓦片加载事件(tileloadstart)、瓦片加载完成事件(tileloadend)以及瓦片加载错误事件(tileloadend)。 并通过OpenLayers使用瓦片加载事件通过实现瓦片加载进度条的案例,实现进度条根据瓦片加…

vue3 vant上传图片

在 Vue 3 中使用 Vant 组件库进行图片上传&#xff0c;您可以使用 Vant 的 ImageUploader 组件。ImageUploader 是 Vant 提供的图片上传组件&#xff0c;可以方便地实现图片上传功能。 以下是一个简单的示例&#xff0c;演示如何在 Vue 3 中使用 Vant 的 ImageUploader 组件进行…

解决Font family [‘sans-serif’] not found问题

序言 以下测试环境都是在 anaconda3 虚拟环境下执行。 激活虚拟环境 conda activate test_python_env 或 source activate test_python_env工具&#xff1a; WinSCP Visual Studio Code 这里笔者使用 WinSCP 工具连接&#xff0c;编辑工具是 Visual Studio Code 一、字体…