1. 平衡树(FHQ-Treap)

1.1. 介绍

功能强大的平衡树,以至于我根本没学 Treap 以及 Splay(LCT 里的另谈),缺点就大概是常数大。

FHQ-Treap 核心操作在于分裂与合并

因为合并时按照随机权值决定父子关系,期望复杂度为 \(\log n\)。

1.1.1 分裂

按照权值 \(val\) 分裂,给定 \(val\),将整棵平衡树 \(T\) 分裂为 \(T_x,T_y\) 使得 \(\forall i\in T_x,\forall j\in T_y\),有 \(val_i\le val<val_j\)。

对于当前节点 \(rt\),若 \(val_rt\le val\),则左子树以及 \(rt\) 都应分到 \(T_x\) 中,故只需向右递归右子节点;反之类似地递归左子节点。

void split(int rt,int &x,int &y,int val)
{
if(!rt){x=y=0;return;}
if(t[rt].val<=val)split(t[rt].rs,t[x=rt].rs,y,val);
else split(t[rt].ls,x,t[y=rt].ls,val);
push_up(rt);
return;
}

其中 push_up() 函数是用来更新节点信息的,一定不要忘写。

1.1.2 合并

给定两棵树 \(T_x,T_y\),前提是 \(\forall i\in T_x,\forall j\in T_y\),有 \(val_i<val_j\)。

对于要合并的 \(x,y\),我们根据优先级决定父子关系,根据 \(val_x<val_y\) 调用 merge() 合并对应子节点。

int merge(int x,int y)
{
if(!x||!y)return x|y;
if(t[x].key<t[y].key){t[x].rs=merge(t[x].rs,y);push_up(x);return x;}
t[y].ls=merge(x,t[y].ls);
push_up(y);
return y;
}

1.2. 其他功能

1.2.1. 插入值

按照 \(val-1\) 分裂为两棵树,再新建节点合并即可。

void ins(int val)
{
int x=0,y=0;
split(root,x,y,val-1);
root=merge(merge(x,new_node(val)),y);
return;
}

其中 new_node() 函数用于新建节点,返回节点编号,\(\mathrm{root}\) 为整棵平衡树的根。

1.2.2. 删除值(只删一个)

void del(int val)
{
int x=0,y=0,z=0;
split(root,x,z,val),split(x,x,y,val-1);
root=merge(merge(x,y=merge(t[y].ls,t[y].rs)),z);
return;
}

1.2.3. 查询值的排名

按照 \(val-1\) 分裂为 \(T_x,T_y\),\(siz_x+1\) 即答案,记得合并回来。

int rk(int val)
{
int x=0,y=0,res=0;
split(root,x,y,val-1);
res=t[x].siz+1;
root=merge(x,y);
return res;
}

1.2.4. 查询第 \(k\) 大

类似线段树上二分,若 \(k\le siz_{ls_{rt}}\) 则向左子节点递归,若 \(k=siz_{ls_{rt}}+1\) 则返回 \(val_{rt}\),否则向右子节点递归。

int kth(int k)
{
int now=root;
while(true)
{
if(k<=t[t[now].ls].siz)now=t[now].ls;
else if(k==t[t[now].ls].siz+1)return t[now].val;
else k-=t[t[now].ls].siz+1,now=t[now].rs;
}
}

1.2.5. 查找严格小于 / 大于 \(val\) 的前驱 / 后继

类似二分(可能),与上一过程类似。也可以采用分裂后找值。

int pre(int val)
{
int now=root,res=0;
while(true)
{
if(!now)return res;
if(val<=t[now].val)now=t[now].ls;
else res=t[now].val,now=t[now].rs;
}
}
int suf(int val)
{
int now=root,res=0;
while(true)
{
if(!now)return res;
if(val>=t[now].val)now=t[now].rs;
else res=t[now].val,now=t[now].ls;
}
}

1.2.6. 区间翻转

此操作要求维护序列平衡树,考虑维护每个位置的值,此时平衡树并不需要满足二叉搜索树性质。

修改时将修改区间按照 \(siz\) 分离出来,打翻转标记即可。

1.3. 例题

\(\color{royalblue}{P3369}\)

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
//IO
inline int rd()
{
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
//--------------------//
const int N=1e5+5; int n;
//--------------------//
struct FHQ_Treap
{
struct FHQ_Treap_Node
{
int siz,val,key;
int ls,rs;
}t[N];
int tot,root; int new_node(int val)
{
t[++tot].val=val;
t[tot].key=rand();
t[tot].siz=1;
return tot;
}
void push_up(int rt)
{
t[rt].siz=t[t[rt].ls].siz+t[t[rt].rs].siz+1;
return;
}
void split(int rt,int &x,int &y,int v)
{
if(!rt){x=y=0;return;}
if(t[rt].val<=v)split(t[rt].rs,t[x=rt].rs,y,v);
else split(t[rt].ls,x,t[y=rt].ls,v);
push_up(rt);
return;
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
if(t[x].key<t[y].key){t[x].rs=merge(t[x].rs,y);push_up(x);return x;}
t[y].ls=merge(x,t[y].ls);
push_up(y);
return y;
}
void ins(int val)
{
int x=0,y=0;
split(root,x,y,val-1);
root=merge(merge(x,new_node(val)),y);
return;
}
void del(int val)
{
int x=0,y=0,z=0;
split(root,x,z,val),split(x,x,y,val-1);
root=merge(merge(x,y=merge(t[y].ls,t[y].rs)),z);
return;
}
int rk(int val)
{
int x=0,y=0,res=0;
split(root,x,y,val-1);
res=t[x].siz+1;
root=merge(x,y);
return res;
}
int kth(int k)
{
int now=root;
while(true)
{
if(k<=t[t[now].ls].siz)now=t[now].ls;
else if(k==t[t[now].ls].siz+1)return t[now].val;
else k-=t[t[now].ls].siz+1,now=t[now].rs;
}
}
int pre(int val)
{
int now=root,res=0;
while(true)
{
if(!now)return res;
if(val<=t[now].val)now=t[now].ls;
else res=t[now].val,now=t[now].rs;
}
}
int suf(int val)
{
int now=root,res=0;
while(true)
{
if(!now)return res;
if(val>=t[now].val)now=t[now].rs;
else res=t[now].val,now=t[now].ls;
}
}
}F;
//--------------------//
int main()
{
n=rd();
for(int op,x,i=1;i<=n;i++)
{
op=rd(),x=rd();
if(op==1)F.ins(x);
else if(op==2)F.del(x);
else if(op==3)printf("%d\n",F.rk(x));
else if(op==4)printf("%d\n",F.kth(x));
else if(op==5)printf("%d\n",F.pre(x));
else printf("%d\n",F.suf(x));
}
return 0;
}

\(\color{royalblue}{P3391}\)

板子。

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
const int N=1e5+5; struct FHQ_Treap
{
struct FHQ_Treap_Node
{
int val,siz,ls,rs,key;
bool lazy;
}t[N];
int root,tot;
int new_node(int val)
{
t[++tot]={val,1,0,0,rand(),false};
return tot;
}
void tag(int rt)
{
swap(t[rt].ls,t[rt].rs);
t[rt].lazy^=1;
return;
}
void push_up(int rt)
{
t[rt].siz=t[t[rt].ls].siz+t[t[rt].rs].siz+1;
return;
}
void push_down(int rt)
{
if(!t[rt].lazy)
return;
tag(t[rt].ls);
tag(t[rt].rs);
t[rt].lazy^=1;
return;
}
void split(int rt,int &x,int &y,int siz)
{
if(!rt){x=y=0;return;}
push_down(rt);
if(t[t[rt].ls].siz>=siz)split(t[rt].ls,x,t[y=rt].ls,siz);
else split(t[rt].rs,t[x=rt].rs,y,siz-t[t[rt].ls].siz-1);
push_up(rt);
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
push_down(x),push_down(y);
if(t[x].key<t[y].key){t[x].rs=merge(t[x].rs,y);push_up(x);return x;}
t[y].ls=merge(x,t[y].ls);
push_up(y);
return y;
}
void change(int l,int r)
{
int x,y,z;
split(root,x,z,r);
split(x,x,y,l-1);
tag(y);
root=merge(x,merge(y,z));
return;
}
void Tprint(int rt)
{
if(!rt)
return;
push_down(rt);
Tprint(t[rt].ls);
printf("%d ",t[rt].val);
Tprint(t[rt].rs);
return;
}
}T;
int n,m;
//--------------------//
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
T.root=T.merge(T.root,T.new_node(i));
for(int l,r,i=1;i<=m;i++)
scanf("%d%d",&l,&r),T.change(l,r);
T.Tprint(T.root);
return 0;
}

2. 树套树

3.1. 介绍

树套树可以用来维护二维的信息。

顾名思义,树套树就是一个数据结构套上另一个数据结构,一般来讲内部维护的信息都需要可合并,通过外层数据结构合并内层信息来达到维护二维信息的目的。

内层数据结构需要满足动态开点,外层数据结构如果可行则采用树状数组维护以减小常数,一般情况下树状数组套线段树较为通用。

3.2. 常用技巧

3.2.1. 动态的区间内偏序问题

树状数组维护一维,线段树维护另一维,在符合第一维要求的树状数组上查询第二维符合要求的信息,修改是简单的。

3.2.2. 动态第 \(k\) 小

静态第 \(k\) 小采用可持久化线段树,每一个位置都继承前一位置的信息,然后前缀相减。

动态第 \(k\) 小只要在树状数组的维护方式上进行继承信息即可。

3. LCT

3.1. 介绍

3.1.1. 基本概念

LCT 全名 Link-Cut-Tree,动态树,是用来维护动态森林的数据结构。

它支持以下操作(需要保证任意操作时刻维护的都为森林):

  • 连边。
  • 断边。
  • 换根。
  • 提取路径信息。

LCT 的大体思路是将每棵树拆分为若干条链,并用平衡树维护链中节点,维护关键字为节点深度,每一条链的状态都是动态的。

由于 LCT 结构的特殊性,一般我们用 Splay 维护链,更加抽象的,我们实际上并不直接利用类似 \(\mathrm{build}\) 的东西将树划分成若干链,我们在需要的时候动态维护我们需要的链,这基于了 Splay 方便的各种操作。

在维护的时候,对于每一个节点,我们选出至多一个儿子作为实儿子,令节点到实儿子的边为实边,剩余边为虚边。实边组成的链成为实链,因为实链是我们所选择的,所以 LCT 的才是灵活多变的,请牢记我们的实链是不断变化去维护信息的。

3.1.2. 辅助树

我们用 Splay 以节点深度为关键字维护每条实链上的点,虚边连接了一棵棵 Splay,每个 Splay 的根节点都有一条虚边指向其维护原链顶的父节点。以这样的方式连接,一棵树上的多个 Splay 就组成了一棵辅助树,多棵辅助树就构成了我们的 LCT。

一颗辅助树有如下性质:

  • 中序遍历每棵 Splay 得到的点序列,对应其维护链从上至下的一条路径。
  • 辅助树节点与原树节点一一对应
  • 每棵 Splay 根节点父节点并不为空,其用虚边指向了此 Splay 维护原链的父节点。需要注意的,根节点并不是其父节点的子节点(认父不认子)。

由于辅助树的以上性质,我们任何操作都不需要维护原树,辅助树可以在任何情况下提取出一棵原树。

需要注意的:

  • 原树的根不等于辅助树的根,原树的父节点指向不等于辅助树的父节点指向。
  • 虚实链是可以在辅助树上进行变换,实现了动态的树链剖分,这也是前文说到的“实链是不断变化去维护信息的”。

至于为什么在操作过程中需要翻转 Splay,目的是保证复杂度,具体证明大概是论文级的,这里不会提及,只需要记住即可。

3.1.3 实现过程

以洛谷模板题为例,进行实现过程的讲解。

维护一个结构体表示一个节点,其中含有以下信息:

\(fa\) \(son_{1/0}\) \(val\) \(sum\) \(siz\) \(lazy\)
父节点 子节点 此节点权值 此节点在 Splay 中的子树异或和 此节点在 Splay 中的子树大小 翻转标记

介绍 LCT 的核心函数之前先给出两个 Splay 的函数(\(t\) 是我们的节点结构体数组)。

void rotate(int rt)
{
int fa=t[rt].fa,gfa=t[fa].fa,gs=get(rt),gfs=get(fa);
bool flag=che_rt(fa);
t[fa].son[gs]=t[rt].son[!gs],t[t[rt].son[!gs]].fa=fa;
t[rt].son[!gs]=fa,t[fa].fa=rt,t[rt].fa=gfa;
if(!flag)
t[gfa].son[gfs]=rt;
push_up(fa),push_up(rt);
return;
}
void splay(int rt)
{
push_down_(rt);
for(int now=t[rt].fa;!che_rt(rt);rotate(rt))
if(!che_rt(now=t[rt].fa))
rotate((get(now)==get(rt))?now:rt);
return;
}

rotate() 函数进行单旋操作,splay() 函数将节点旋至其所在 Splay 的根节点。与正常 Splay 不同的是,我们需要判断一个节点是否为其所在 Splay 的根(che_rt() 函数的作用),同时也请注意翻转标记的下传。

接下来我们介绍 LCT 的核心操作。

void access(int rt)
{
for(int lst=0;rt;lst=rt,rt=t[rt].fa)
{
splay(rt);
t[rt].son[1]=lst;
push_up(rt);
}
return;
}

此函数作用是把某节点到当前辅助树的根路径上的点合并为一棵新的 Splay,具体过程如下:

  • 现将当前节点旋至其所在 Splay 的根。
  • 将上一节点接到当前节点上(这样直接实现了新链的合并与原来 Splay 边的断开)。
  • 更新当前节点信息。
  • 跳转到父亲,重复操作直到跳至根。

我们的剩余函数都以 access() 函数为基础进行操作,接下来介绍其他的函数。

  • tag() 用于对节点打上旋转标记。
void tag(int rt)
{
swap(t[rt].son[0],t[rt].son[1]);
t[rt].lazy^=1;
return;
}
  • che_rt() 用于检查节点是否为 Splay 的根。
bool che_rt(int rt)
{
return (t[t[rt].fa].son[0]!=rt&&t[t[rt].fa].son[1]!=rt);
}
  • get() 用于找到当前节点是其父节点的哪个儿子。
int get(int rt)
{
return ((t[t[rt].fa].son[1]==rt)?1:0);
}
  • push_up() 更新当前节点信息。
void push_up(int rt)
{
t[rt].siz=t[t[rt].son[0]].siz+t[t[rt].son[1]].siz+1;
t[rt].sum=t[t[rt].son[0]].sum^t[t[rt].son[1]].sum^t[rt].val;
return;
}
  • push_down() 下传标记。
void push_down(int rt)
{
if(t[rt].lazy!=0)
{
tag(t[rt].son[0]);
tag(t[rt].son[1]);
t[rt].lazy=0;
}
return;
}
  • push_down_() 下传当前节点至 Splay 根节点的标记(从上至下)。
void push_down_(int rt)
{
if(!che_rt(rt))
push_down_(t[rt].fa);
push_down(rt);
}
  • make_rt() 使当前节点变为辅助树的根。
void make_rt(int rt)
{
access(rt);
splay(rt);
tag(rt);
return;
}
  • find_rt() 查询当前节点所在辅助树的根(请考虑 splay() 的过程,不难得出结论)。
int find_rt(int rt)
{
access(rt);
splay(rt);
while(t[rt].son[0])
{
push_down(rt);
rt=t[rt].son[0];
}
splay(rt);
return rt;
}
  • check() 查询两节点是否连通。
bool check(int x,int y)
{
make_rt(x);
return (find_rt(y)==x);
}
  • link() 连边(先判连通)。
void link(int x,int y)
{
if(!check(x,y))
t[x].fa=y;
}
  • cut() 断边(判相连,仍然考虑实现过程即可理解)。
void cut(int x,int y)
{
if(check(x,y)&&t[x].siz<=2)
t[y].fa=t[x].son[1]=0;
}
  • split() 分离一条链,正是前文提到的“提取路径信息”。
int split(int x,int y)
{
make_rt(x);
access(y);
splay(y);
return y;
}
  • change() 单点修改(并不是所有的题都有)。
void change(int rt,int val)
{
splay(rt);
t[rt].val=val;
push_up(rt);
return;
}

对于模板题的构建至此结束,LCT 的拓展性较强,具体题目还需具体分析。

3.2. 常用技巧

3.2.1 基础技巧

对于维护操作应多考虑优化,可以复杂化 push_up() 函数,但要注意代码结构的简洁性,并尽可能减少代码量与数据结构的操作难度,即使可能使复杂度变为稍劣(在无风险前提下)。

3.2.?咕咕咕

咕咕咕

3.3. 题目

\(\color{blueviolet}{P3690}\)

板子。

$\text{Code}$:
#include<bits/stdc++.h>
#define LL long long
#define UN unsigned
using namespace std;
//--------------------//
const int N=1e5+5; struct LCT
{
struct Tree_Node
{
int fa,son[2];
int val,sum,siz;
int lazy;
}t[N];
void tag(int rt)
{
swap(t[rt].son[0],t[rt].son[1]);
t[rt].lazy^=1;
return;
}
bool che_rt(int rt)
{
return (t[t[rt].fa].son[0]!=rt&&t[t[rt].fa].son[1]!=rt);
}
int get(int rt)
{
return ((t[t[rt].fa].son[1]==rt)?1:0);
}
void push_up(int rt)
{
t[rt].siz=t[t[rt].son[0]].siz+t[t[rt].son[1]].siz+1;
t[rt].sum=t[t[rt].son[0]].sum^t[t[rt].son[1]].sum^t[rt].val;
return;
}
void push_down(int rt)
{
if(t[rt].lazy!=0)
{
tag(t[rt].son[0]);
tag(t[rt].son[1]);
t[rt].lazy=0;
}
return;
}
void push_down_(int rt)
{
if(!che_rt(rt))
push_down_(t[rt].fa);
push_down(rt);
}
void rotate(int rt)
{
int fa=t[rt].fa,gfa=t[fa].fa;
int gs=get(rt),gfs=get(fa);
bool flag=che_rt(fa);
t[fa].son[gs]=t[rt].son[!gs];
t[t[rt].son[!gs]].fa=fa; t[rt].son[!gs]=fa;
t[fa].fa=rt;
t[rt].fa=gfa;
if(!flag)
t[gfa].son[gfs]=rt;
push_up(fa),push_up(rt);
return;
}
void splay(int rt)
{
push_down_(rt);
for(int now=t[rt].fa;!che_rt(rt);rotate(rt))
if(!che_rt(now=t[rt].fa))
rotate((get(now)==get(rt))?now:rt);
return;
}
void access(int rt)
{
for(int lst=0;rt;lst=rt,rt=t[rt].fa)
{
splay(rt);
t[rt].son[1]=lst;
push_up(rt);
}
return;
}
void make_rt(int rt)
{
access(rt);
splay(rt);
tag(rt);
return;
}
int find_rt(int rt)
{
access(rt);
splay(rt);
while(t[rt].son[0])
{
push_down(rt);
rt=t[rt].son[0];
}
splay(rt);
return rt;
}
bool check(int x,int y)
{
make_rt(x);
return (find_rt(y)==x);
}
void link(int x,int y)
{
if(!check(x,y))
t[x].fa=y;
}
void cut(int x,int y)
{
if(check(x,y)&&t[x].siz<=2)
t[y].fa=t[x].son[1]=0;
}
int split(int x,int y)
{
make_rt(x);
access(y);
splay(y);
return y;
}
void change(int rt,int val)
{
splay(rt);
t[rt].val=val;
push_up(rt);
return;
}
}L;
//--------------------//
int n,m;
//--------------------//
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&L.t[i].val);//L.t[i].sum=L.t[i].val;
for(int op,x,y,i=1;i<=m;i++)
{
scanf("%d%d%d",&op,&x,&y);
if(op==0)
printf("%d\n",L.t[L.split(x,y)].sum);
else if(op==1)
L.link(x,y);
else if(op==2)
L.cut(x,y);
else
L.change(x,y);
}
return 0;
}

「Note」数据结构方向 - 数据结构进阶的更多相关文章

  1. 「NOTE」常系数齐次线性递推

    要不是考到了,我还没发现这玩意我不是很会-- # 前置 多项式取模: 矩阵快速幂. # 常系数齐次线性递推 描述的是这么一个问题,给定数列 \(c_1,c_2,\dots,c_k\) 以及数列 \(f ...

  2. 「HNOI2016」数据结构大毒瘤

    真是 \(6\) 道数据结构毒瘤... 开始口胡各种做法... 「HNOI2016」网络 整体二分+树状数组. 开始想了一个大常数 \(O(n\log^2 n)\) 做法,然后就被卡掉了... 发现直 ...

  3. 「MoreThanJava」Day 5:面向对象进阶——继承详解

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  4. Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门

      进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...

  5. 「MoreThanJava」Day 6:面向对象进阶——多态

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  6. 《Offer一箩筐》一份高质量「简历」撰写指南,望打扰!!

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」. 如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力! Hi~ 这里是 ...

  7. 一个「学渣」从零开始的Web前端自学之路

    从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”. 最后的机缘巧合下,走上了前端开发之路,作为一个非计算机专业且低 ...

  8. LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)

    题意 给你一颗 \(n\) 个点的树,每个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操作. 需要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle ...

  9. 「MoreThanJava」当大学选择了计算机之后应该知道的

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  10. 「干货」面试官问我如何快速搜索10万个矩形?——我说RBush

    「干货」面试官问我如何快速搜索10万个矩形?--我说RBUSH 前言 亲爱的coder们,我又来了,一个喜欢图形的程序员‍,前几篇文章一直都在教大家怎么画地图.画折线图.画烟花,难道图形就是这样嘛,当 ...

随机推荐

  1. 【译】Visual Studio 中新的强大生产力特性

    有时候,生活中的小事才是最重要的.在最新版本的 Visual Studio 中,我们增加了一些功能和调整,目的是让您脸上带着微笑,让您更有效率.这里是其中的一些列表,如果您想要完整的列表,请查看发行说 ...

  2. Vue3生命周期钩子函数深度解析:从源码到实战的万字指南

    一.Vue3生命周期革新特性 相较于Vue2,Vue3通过Composition API带来了更灵活的生命周期管理方式.通过onBeforeMount等函数注册钩子时,实际是通过injectHook方 ...

  3. Java24你发任你发,我用Java8

    大家好,我是晓凡. 各位 Java 开发者们!是不是还在为 Java 23 的新特性忙得焦头烂额? 别急,Java 24 已经悄咪咪地发布了! 这可是自 Java 21 以来的第三个非长期支持版本,而 ...

  4. docker 版本号说明

    17.03 版本以前 Docker CE 在 17.03 版本之前叫 Docker Engine, 版本说明参考这里 => Docker Engine release notes, 可以看到 D ...

  5. 团队小规模本地大模型服务平台搭建 - Ubuntu

    实现目标和考虑因素 部署一个支持多用户同时使用.多模型运行的离线局域网大模型服务器 需要考虑以下几个关键因素: 大模型的加载和管理.使用一个基础大模型,根据实战需要创建多个专用模型,模型管理方便可靠. ...

  6. WIN2012域用户添加和批量添加工具

    WIN2012域用户添加和批量添加,不需要进行复杂的进电脑管理去添加 直接在软件上就可单个用户添加,可批量添加,并把指定的用户加入组 可以自定义组织单位,使用起来比较简单方便. 链接:https:// ...

  7. js 小数取整

    小数取整 var num = 123.456; // 常规方法 console.log(parseInt(num)); // 123 // 双重按位非 console.log(~~num); // 1 ...

  8. Quartz.NET - 教程 8: 调度器监听器

    译者注: 目录在这 Quartz.NET 3.x 教程 原文在这 Lesson 8: SchedulerListeners SchedulerListeners 跟 ITriggerListeners ...

  9. Eclipse java项目转Maven项目

    1.右键项目->configure->选择maven->配置maven的pom.xml 2.在src/main下新建java文件,将原来src下的java文件夹拷贝至该目录下: 3. ...

  10. SpringMVC的执行过程

    环境准备 package org.example.springmvclearn; public record Greeting(long id, String content) { } package ...