[学习笔记]线段树(全)

news2024/10/25 18:55:17

线段树是一种可以处理区间问题的优秀数据结构.
线段树是一颗二叉树, 其中的每一个节点都代表了某个区间的信息.

普通线段树

这里默认您已经会了以下操作:

  1. 建树(以单点修改的形式)
  2. 单点修改/查询
  3. 区间查询

如果不会的话请见OI Wiki

着重讲解区间修改中 tag 的用法

对于区间修改, 如果仍然使用单点修改的方式, 肯定会 TLE. 因此使用 tag 进行暂存操作

用于更新/与原来不同的函数: pushdown 函数, update 函数, query 函数

Pushdown

对于当前的节点 x x x , 把它身上的 tag 推给他的儿子, 并且直接给它的两个儿子加上区间增加的和

	void pushdown(int x,int l,int r){
        if(tag[x]!=0){
            tag[x<<1]+=tag[x];
            tag[x<<1|1]+=tag[x];
            int mid=(l+r)>>1;
            t[x<<1]+=(mid-l+1)*tag[x];
            t[x<<1|1]+=(r-mid)*tag[x];
            tag[x]=0;
        }
    }

Update

对于当前节点 x x x , 我们先要判断当前节点代表区间在不在修改区间范围内, 如果是, 修改当前 tag 值, 并直接对当前区间加上区间增加和.

如果不是, 就要把自己的 tag 值推给两个儿子(因为当前这个点所包含的区间无法完全囊括要修改的区间), 之后进行二分, 看往左/右继续更新

    void update(int x,int l,int r,int L,int R,int val){
        if(l>=L && r<=R){
            tag[x]+=val;
            t[x]+=(r-l+1)*val;
            return;
        }
        int mid=(l+r)>>1;
        pushdown(x,l,r);
        if(mid>=L){
            update(x<<1,l,mid,L,R,val);
        }
        if(mid<R){
            update(x<<1|1,mid+1,r,L,R,val);
        }
        t[x]=t[x<<1]+t[x<<1|1];
    }

Query

与 Update 类似, 对于当前节点 x x x ,如果它再查询区间内, 直接返回该该点值, 要么就向下更新, 继续查询

    int query(int x,int l,int r,int L,int R){
        if(l>=L && r<=R){
            return t[x];
        }
        pushdown(x,l,r);
        int mid=(l+r)>>1,ans=0;
        if(mid>=L){
            ans+=query(x<<1,l,mid,L,R);
        }
        if(mid<R){
            ans+=query(x<<1|1,mid+1,r,L,R);
        }
        return ans;
    }

最后把这些步骤整合到一起, 就形成了可以进行完整区间操作的线段树

Scode

#include <bits/stdc++.h>
using namespace std;
namespace Radon{
    void Main();
}
int main(){
    Radon::Main();
    return 0;
}

namespace Radon{
    #define N 1000102
    #define int long long


    int n,m;

    int t[N<<2],tag[N<<2];


    void pushdown(int x,int l,int r){
        if(tag[x]!=0){
            tag[x<<1]+=tag[x];
            tag[x<<1|1]+=tag[x];
            int mid=(l+r)>>1;
            t[x<<1]+=(mid-l+1)*tag[x];
            t[x<<1|1]+=(r-mid)*tag[x];
            tag[x]=0;
        }
    }
    void build(int x,int l,int r,int num,int val){
        if(l==r){
            t[x]=val;
            return;
        }
        int mid=(l+r)>>1;
        if(mid>=num){
            build(x<<1,l,mid,num,val);
        }else{
            build(x<<1|1,mid+1,r,num,val);
        }
        t[x]=t[x<<1]+t[x<<1|1];
    }
    void update(int x,int l,int r,int L,int R,int val){
        if(l>=L && r<=R){
            tag[x]+=val;
            t[x]+=(r-l+1)*val;
            return;
        }
        int mid=(l+r)>>1;
        pushdown(x,l,r);
        if(mid>=L){
            update(x<<1,l,mid,L,R,val);
        }
        if(mid<R){
            update(x<<1|1,mid+1,r,L,R,val);
        }
        t[x]=t[x<<1]+t[x<<1|1];
    }
    int query(int x,int l,int r,int L,int R){
        if(l>=L && r<=R){
            return t[x];
        }
        pushdown(x,l,r);
        int mid=(l+r)>>1,ans=0;
        if(mid>=L){
            ans+=query(x<<1,l,mid,L,R);
        }
        if(mid<R){
            ans+=query(x<<1|1,mid+1,r,L,R);
        }
        return ans;
    }


    void Main(){
        ios::sync_with_stdio(0);
        cin.tie(0);cout.tie(0);
        cin >> n >> m;
        for(int x=1;x<=n;x++){
            int a;
            cin >> a;
            build(1,1,n,x,a);
        }
        for(int x=1;x<=m;x++){
            int opt,a,b;
            cin >> opt >> a >> b;
            if(opt==1){
                int w;
                cin >> w;
                update(1,1,n,a,b,w);
            }else{
                cout << query(1,1,n,a,b) << endl;
            }
        }
    }
}

动态开点线段树

动态开点, 与 vector 的原理差不多, 都是用到哪些点, 就使用哪些点.

所以线段树会发生以下改变 :

  1. 再建树的时候, 不在使用 x < < 1 x<<1 x<<1 , x < < 1 ∣ 1 x<<1|1 x<<1∣1 代表 l s o n lson lson r s o n rson rson , 而是使用以下操作:

    	void build(int &x,int l,int r,int num,int val){
            if(x==0){
                x=++cnt;
            }
            if(l==r){
                t[x]=val;
                return;
            }
            int mid=(l+r)>>1;
            if(num<=mid){
                build(ls[x],l,mid,num,val);
            }else{
                build(rs[x],mid+1,r,num,val);
            }
            t[x]=t[ls[x]]+t[rs[x]];
        }
    

    其中, c n t cnt cnt 表示已经使用的节点数量(也可以认为是要使用的下一个节点的编号), 而使用 int &x 可以做到直接更改 ls[x]rs[x] 代表的下标

  2. 对于一个节点 x x x , 他的左右儿子不再是 x < < 1 x<<1 x<<1 , x < < 1 ∣ 1 x<<1|1 x<<1∣1, 而是变成了某个编号. (用 l s o n lson lson r s o n rson rson 记录)

  3. 其他的地方与正常的线段树完全相同, 只需要改变左右儿子的表达方式即可

Scode

#include <bits/stdc++.h>
using namespace std;
namespace Radon{
    void Main();
}
int main(){
    Radon::Main();
    return 0;
}

namespace Radon{
    #define int long long
    #define N 100010*2


    int n,m;
    int ls[N],rs[N];
    int tag[N];
    int t[N];
    int cnt=1;//这里初值要赋为1的原因是,我们默认表示区间1~n的节点是存在的,不需要额外去新建

    void pushdown(int x,int l,int r){
        if(tag[x]!=0){
            if(ls[x]==0){
                ls[x]=++cnt;
            }
            if(rs[x]==0){
                rs[x]=++cnt;
            }
            tag[ls[x]]+=tag[x];
            tag[rs[x]]+=tag[x];
            int mid=(l+r)>>1;
            t[ls[x]]+=(mid-l+1)*tag[x];
            t[rs[x]]+=(r-mid)*tag[x];
            tag[x]=0;
        }
    }

    void build(int &x,int l,int r,int num,int val){
        if(x==0){
            x=++cnt;
        }
        if(l==r){
            t[x]=val;
            return;
        }
        int mid=(l+r)>>1;
        if(num<=mid){
            build(ls[x],l,mid,num,val);
        }else{
            build(rs[x],mid+1,r,num,val);
        }
        t[x]=t[ls[x]]+t[rs[x]];
    }

    void update(int &x,int l,int r,int L,int R,int val){
        if(x==0){
            x=++cnt;
        }
        if(l>=L && r<=R){
            tag[x]+=val;
            t[x]+=(r-l+1)*val;
            return;
        }
        int mid=(l+r)>>1;
        pushdown(x,l,r);
        if(L<=mid){
            update(ls[x],l,mid,L,R,val);
        }
        if(R>mid){
            update(rs[x],mid+1,r,L,R,val);
        }
        t[x]=t[ls[x]]+t[rs[x]];
    }

    int query(int x,int l,int r,int L,int R){
        if(x==0){
            return 0;
        }
        if(l>=L && r<=R){
            return t[x];
        }
        pushdown(x,l,r);
        int ans=0,mid=(l+r)>>1;
        if(mid>=L){
            ans+=query(ls[x],l,mid,L,R);
        }
        if(mid<R){
            ans+=query(rs[x],mid+1,r,L,R);
        }
        return ans;
    }


    void Main(){
        ios::sync_with_stdio(0);
        cin.tie(0);cout.tie(0);
        cin >> n >> m;
        for(int x=1;x<=n;x++){
            int a;
            cin >> a;
            int num=1;
            build(num,1,n,x,a);
        }
        for(int x=1;x<=m;x++){
            int opt,a,b;
            cin >> opt >> a >> b;
            if(opt==1){
                int qw;
                cin >> qw;
                int num=1;
                update(num,1,n,a,b,qw);
            }else{
                cout << query(1,1,n,a,b) << endl;
            }
        }
    }
}

权值线段树

注 : 普通权值线段树只能做到对某个固定区间求某些数的出现个数, 如果要求动态区间, 请见可持久化线段树

权值线段树可以统计固定区间内总数的出现个数

这时候每个节点的含义就变为了它所代表的区间为值域, 每个数出现次数的总和

eg : 若有一个区间 { 1 , 5 , 2 , 3 , 4 , 1 , 3 , 4 , 4 , 4 } \{ 1,5,2,3,4,1,3,4,4,4 \} {1,5,2,3,4,1,3,4,4,4}, 那么它在权值线段树里长这样 :

在这里插入图片描述
次图片来源于添加链接描述

其中, 最下边的叶子节点的值代表某个值出现的次数

容易发现, 权值线段树的大小与值域有关, 如果 r ≤ 1 0 18 r\le10^{18} r1018 就会爆掉, 因此使用动态开点

具体实现 :

Update(Build)

使用 update 进行建树操作, 本质就是对于某个节点下标 +1

    void update(int &x,int l,int r,int num,int val){
        if(x==0){
            x=++cnt;
        }
        // cout << x << ':' << l << ' ' << r << ' ' << num << endl;
        if(l==r){
            t[x]+=val;
            return;
        }
        int mid=(l+r)>>1;
        if(mid>=num){
            update(ls[x],l,mid,num,val);
        }else{
            update(rs[x],mid+1,r,num,val);
        }
        t[x]=t[ls[x]]+t[rs[x]];
    }

Query

正常按照线段树查询即可

例题 : P1908 逆序对

把每个数依次加到线段树中, 并且查询当前比他大的数有多少即可

Scode

#include <bits/stdc++.h>
using namespace std;
namespace Radon{
    void Main();
}
int main(){
    Radon::Main();
    return 0;
}

namespace Radon{
    #define LL long long
    //十点OI一场空,__ __ __ __ __ __ __
    #define INF 1000000010
    #define N 10000010
    int n;
    LL a[N];
    int t[N],ls[N],rs[N];
    LL cnt=1;


    void update(int &x,int l,int r,int num,int val){
        if(x==0){
            x=++cnt;
        }
        // cout << x << ':' << l << ' ' << r << ' ' << num << endl;
        if(l==r){
            t[x]+=val;
            return;
        }
        int mid=(l+r)>>1;
        if(mid>=num){
            update(ls[x],l,mid,num,val);
        }else{
            update(rs[x],mid+1,r,num,val);
        }
        t[x]=t[ls[x]]+t[rs[x]];
    }

    LL query(int x,int l,int r,int L,int R){
        if(x==0){
            return 0;
        }
        if(l>=L && r<=R){
            return t[x];
        }
        int ans=0,mid=(l+r)>>1;
        if(mid>=L){
            ans+=query(ls[x],l,mid,L,R);
        }
        if(mid<R){
            ans+=query(rs[x],mid+1,r,L,R);
        }
        return ans;
    }


    void Main(){
        ios::sync_with_stdio(0);
        cin.tie(0);cout.tie(0);
        LL ans=0;
        cin >> n;
        for(int x=1;x<=n;x++){
            cin >> a[x];
            int temp=1;
            update(temp,1,INF,a[x],1);
            ans+=query(1,1,INF,a[x]+1,INF-5);
            // cout << a[x] << ':' << query(1,1,INF,a[x]+1,INF);
        }
        cout << ans;
    }
}

可持久化线段树(主席树)

单点修改, 区间查询

主席树必须使用动态开点

可持久化, 顾名思义, 就是可以保存之前某个状态的一种方式.

由于单点修改的性质, 每次修改只会更改一条链上的值(如下图, 如果我要修改 8 8 8 节点, 只会对红色编号的节点修改).

因此如果我想查询过去某个版本线段树上的信息, 直接对每个版本都建立线段树会 TLE+MLE, 因此我们可以尝试每次在原树 (或者说实在哪个版本上修改, 就以哪个版本为原树) 新建一条链即可. 这时候就相当于又有了一颗线段树, 需要储存其根节点的编号

如下图 :

白色的 1 ∼ 15 1\sim15 115 节点为线段树的版本一(可以视为初始状态)

这时候如果我要令 a [ 1 ] a[1] a[1] ++(即白色 8 8 8 号节点 ++), 正常流程是修改白色 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8 节点权值, 但是这样做就会我们失去版本一 (原树) 的部分信息. 因此我们直接新建一条红色的链, 链里存放的是修改完后的状态

红色的 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8 和 白色的剩余节点(不包括白色的 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8 )为线段树的版本二

在这里插入图片描述

我们把白色的 1 1 1 认作是版本一线段树的根, 而红色的 1 1 1 被认为是版本二的根

对于这条链上的每一个节点 (如红 1 1 1 ) , 可以发现它和它的母体节点 (如白 $1 $ ) 有相似之处 : 新节点的权值可以由母体节点转移来, 新节点的左右儿子部分与母体节点相同.

因此我们需要按顺序进行以下操作, 来完成克隆一份母体节点, 作为新节点的初始值:

  1. 把母体节点的信息完全复制一份

    如红 1 1 1 节点需要复制白 1 1 1 的全部信息, 包括左右儿子编号, 节点内存储的值

  2. 将当前节点的编号更改, 变成一个新的点(与动态开点密切相关)

  3. 根据题中条件向左/右儿子继续递归新建

    可以使用 &ls[x] 的方式直接改变左右儿子的编号值(利用递归到左右儿子时进行的操作 2 2 2 )

如果知道了过去查询的版本号, 直接从那个版本的根开始查询即可

现在考虑如何操作 :

	void build(int &x,int l,int r,int num,int val){
        if(x==0){x=++cnt;}
        if(l==r){t[x]=val;return;}
        int mid=(l+r)>>1;
        if(num<=mid){ build(ls[x],l,mid,num,val); }
        if(mid<r){ build(rs[x],mid+1,r,num,val); }
        t[x]=t[ls[x]]+t[rs[x]];//整合信息, 不一定是加
    }

Build

对于简述操作, 和动态开点线段树完全相同. 但是某些时候, 建树操作可以被更新(插入)操作代替. 如 P3834 【模板】可持久化线段树 2

    int clone(int x){
        t[++cnt]=t[x];  ls[cnt]=ls[x];  rs[cnt]=rs[x];
        return cnt;
    }

Clone

直接把目标节点的信息复制到新节点中, 并且传回新节点编号 c n t cnt cnt

    void insert(int &x,int l,int r,int val){
        x=clone(x);
        if(l==r){t[x]++;return;}
        int mid=(l+r)>>1;
        if(val<=mid){insert(ls[x],l,mid,val);
        }else{insert(rs[x],mid+1,r,val);}
        t[x]=t[ls[x]]+t[rs[x]];
    }

Insert

用于更改变量. 先复制一份节点, 然后递归更改.

需要注意的是, 为了保存不同版本线段树根的编号, 在初次更新/建树时需要按照以下方式传参:

	rt[x]=rt[x-1];
//不一定是从x-1上复制节点,依据题干,在哪个版本上修改,就复制哪个根的信息

	insert(rt[x],0,INF,a);

首先, 先复制一份

然后, 直接向内传 rt[x]( r o o t x root_x rootx , x x x 版本线段树的根). 通过 &x 的特性, 可以直接更改 rt[x] 的编号

注 : rt[x]t[x]不同, rt只存储节点编号, x x x 代表线段树版本. 而t储存的是第 x x x 个节点的权值.

Scode

#include <bits/stdc++.h>
using namespace std;
namespace Radon{
    void Main();
}
int main(){
    Radon::Main();
    return 0;
}


namespace Radon{
    #define INF 1000000010
    #define N 200010
    #define vvv 5

    int n,m;
    int rt[N<<vvv];
    int t[N<<vvv];
    int ls[N<<vvv],rs[N<<vvv];
    int cnt=0;



    int clone(int x){
        t[++cnt]=t[x];
        ls[cnt]=ls[x];
        rs[cnt]=rs[x];
        return cnt;
    }

    void build(int &x,int l,int r,int num,int val){
        if(x==0){
            x=++cnt;
        }
        if(l==r){
            t[x]=val;
            return;
        }
        int mid=(l+r)>>1;
        if(num<=mid){
            build(ls[x],l,mid,num,val);
        }
        if(mid<r){
            build(rs[x],mid+1,r,num,val);
        }
        t[x]=t[ls[x]]+t[rs[x]];
    }

    void insert(int &x,int l,int r,int val){
        x=clone(x);
        if(l==r){
            t[x]++;
            return;
        }
        int mid=(l+r)>>1;
        if(val<=mid){
            insert(ls[x],l,mid,val);
        }else{
            insert(rs[x],mid+1,r,val);
        }
        t[x]=t[ls[x]]+t[rs[x]];
    }

    int query(int xr,int xl,int l,int r,int k){
        // cout << xr << ' ' << xl << ':' << l << ' ' << r << ' ' << k << endl;
        if(l>=r){
            return l;
        }
        int mid=(l+r)>>1;
        int num=t[ls[xr]]-t[ls[xl]];
        if(num>=k){
            return query(ls[xr],ls[xl],l,mid,k);
        }else{
            return query(rs[xr],rs[xl],mid+1,r,k-num);
        }
    }



    void Main(){
        // freopen("")
        ios::sync_with_stdio(0);
        cin.tie(0);cout.tie(0);
        cin >> n >> m;
        for(int x=1;x<=n;x++){
            int a;
            cin >> a;
            // rt[x]=rt[x-1];
            // rt[x]=cnt+1;
            rt[x]=rt[x-1];
            insert(rt[x],0,INF,a);
        }
        // cout << "___________________________\n";
        for(int x=1;x<=m;x++){
            int a,b,c;
            cin >> a >> b >> c;
            cout << query(rt[b],rt[a-1],0,INF,c) << '\n';
        }
    }
}

标记永久化正在学习, 马上补

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

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

相关文章

InternVL-1.1: Enhance Chinese and OCR Capabilities

Blog:https://internvl.github.io/blog/2024-01-24-InternVL-1.1/ 指南:https://internvl.readthedocs.io/en/latest/internvl1.1/introduction.html InternVL-Chat-V1-1 结构类似于 LLaVA,包括一个 ViT、一个 MLP 投影器和一个 LLM。如上图所示,我们通过一个简单的 MLP …

ubuntu服务器离线安装pytorch(cpu版本)

一、查看服务器是否有nvidia显卡&#xff08;无输出则没有nvidia显卡&#xff0c;则不需要安装nvidia驱动、cuda、cudnn&#xff09; lspci | grep -i nvidia 二、本地下载对应版本的torch&#xff08;对应python版本和linux系统&#xff09; 注意&#xff1a;cpu版本&#…

iOS调试真机出现的 “__llvm_profile_initialize“ 错误

一、错误形式&#xff1a; app启动就崩溃&#xff0c;如下&#xff1a; Demo__llvm_profile_initialize:0x1045f7ab0 <0>: stp x20, x19, [sp, #-0x20]!0x1045f7ab4 <4>: stp x29, x30, [sp, #0x10]0x1045f7ab8 <8>: add x29, sp, #0x100x1…

python最新h5st4.9.1调用源码(2025-10-25)

废话不多说&#xff0c;直接上源码&#xff0c;需要技术支持的私。 一、调用js方法&#xff1a; # -*- coding: utf-8 -*- """ -------------------------------------------------Author: byc6352File: jdh5st.pyTime: 2024/10/25 08:03Technical Support:by…

Python 20个必学实例盘点

Python 1. 计算器程序&#xff1a; def add(x, y): return x ydef subtract(x, y): return x - ydef multiply(x, y): return x * ydef divide(x, y): try: return x / y except ZeroDivisionError: print("除数不能…

基于SpringBoot+Vue+uniapp微信小程序的文玩销售小程序的详细设计和实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

【网路原理】——HTTP状态码和Postman使用

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 引入&#xff1a; 一&#xff1a;状态码 1&#xff1a;状态码标准 2&#xff1a;常见的状态码 ①2…

依托微信小程序,畅享校园二手交易

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

普通数组矩阵

文章目录 普通数组一、最大子数组二、合并区间三、轮转数组四、除自身以外数组的乘积五、缺失的第一个正数 普通数组 一、最大子数组 题目链接 方法一&#xff1a;动态规划 方法二&#xff1a;前缀和&#xff08;有点难理解&#xff09; 二、合并区间 题目链接 三、轮…

Linux中部署Mysql保姆级教程

一、版本说明 本文的版本号是5.7.30,5.6及以上版本的MySQL要求Linux系统虚拟内存不能小于1G,否则MySQL可能无法运行。 二、安装前的准备 2.1查看系统自带的Mariadb rpm -qa|grep mariadb 安装mysql为什么需要卸载mariadb: 以前的Linux系统中数据库大部分是mysql,…

android 利用adb将apk安装到模拟器中的方法

1、安装完成了sdk以后&#xff0c;会有一个工具集&#xff0c;里面有一个adb.exe&#xff0c;这个文件可以查看模拟器列表&#xff0c;及安装apk到模拟器中。 可以将这个目录加到环境变量中&#xff0c;这样就不用定位到目录&#xff0c; 然后使用adb命令了。 2、这里我们先定…

纯血鸿蒙:国产操作系统的崛起与安卓的本质区别

华为正式发布纯血鸿蒙&#xff08;Harmony OS 5.0&#xff09;标志着中国科技产业进入了一个崭新的时代。随着这一操作系统的推出&#xff0c;中国不仅在消费电子领域迎来了一个新的操作系统生态&#xff0c;更是加强了中国在全球科技领域的发言权。本文将深入探讨纯血鸿蒙与安…

win10怎么卸载软件干净?电脑彻底删除软件的方法介绍,一键清理卸载残留!

电脑上经常会下载各种各样的软件来协助我们办公&#xff0c;不同的软件能够满足不同的需求。 但是不少软件可能使用频率没有那么高&#xff0c;甚至完全不使用。这个时候就需要将这些不常用的电脑软件卸载掉了&#xff0c;卸载软件能够释放一定的存储空间&#xff0c;提高电脑…

封装echarts组件,即插即用(附源码)

前言&#xff1a;最近一个项目刚收工&#xff0c;分享一个常用的封装echarts的组件。 一、直接上组件代码 <template><el-card class"echart-card" shadow"hover"><template v-slot:header><div class"card-header">&…

[网络协议篇] UDP协议

文章目录 1. 简介2. 特点3. UDP数据报结构4. 基于UDP的应用层协议5. UDP安全性问题6. 使用udp传输数据的系统就一定不可靠吗&#xff1f;7. 基于UDP的主机探活 python实现 1. 简介 User Datagram Protocol&#xff0c;用户数据报协议&#xff0c;基于IP协议提供面向无连接的网…

郑州地铁携手百望云,以数电票平台升级打造坚实便民惠民服务能力

随着城市的快速发展&#xff0c;地铁成为很多大城市市民出行的主力工具。为民众出行提供安全、便捷、合规的出行服务&#xff0c;成为相关机构的重点工作。 近日&#xff0c;郑州地铁集团有限公司&#xff08;以下简称“郑州地铁”&#xff09;与百望云达成合作&#xff0c;开…

免杀对抗—特征码修改花指令资源修改加壳保护

前言 今天主要是讲这个特征码定位以及这个花指令&#xff0c;资源修改这个东西其实有点投机取巧啦&#xff0c;至于这个加壳之前也讲过了&#xff0c;直接工具搞就行。那么什么是特征码定位呢&#xff0c;众所周知&#xff0c;杀软是通过特征码来检测我们的exe是否是后门的&am…

老外说“奶茶”不叫“milk tea”?!那叫什么?柯桥英语口语学习生活日语培训

奶茶的英文怎么说&#xff1f; 其实&#xff0c;“奶茶”在英文中有多种表达方式&#xff0c;但最常见且准确的应该是“tea with milk”。这种表达方式直接描述了奶茶的本质——茶与牛奶的结合。当然&#xff0c;你也可以用“milky tea”来表达&#xff0c;但相对来说&#xf…

探索自然之美:SpringBoot驱动的安康旅游网站开发

第一章 绪论 1.1 研究现状 时代的发展&#xff0c;我们迎来了数字化信息时代&#xff0c;它正在渐渐的改变着人们的工作、学习以及娱乐方式。计算机网络&#xff0c;Internet扮演着越来越重要的角色&#xff0c;人们已经离不开网络了&#xff0c;大量的图片、文字、视频冲击着我…

基于SpringBoot的酒店管理系统的设计与实现

摘要 酒店管理系统的设计旨在提供快捷高效的住宿资源管理方案&#xff0c;帮助管理员实现对酒店内房间、客户信息、订单的全方位管理&#xff0c;同时为用户提供便捷的预订和查询功能。本系统基于Spring Boot框架&#xff0c;结合前端框架和数据库设计&#xff0c;构建一个用户…