树链剖分——从入门到入坟

news2025/2/25 8:07:39

树链剖分的思想及能解决的问题

树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。

具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。

树链剖分(树剖/链剖)有多种形式,如 重链剖分长链剖分 和用于 Link/cut Tree 的剖分(有时被称作「实链剖分」),大多数情况下(没有特别说明时),「树链剖分」都指「重链剖分」。

重链剖分可以将树上的任意一条路径划分成不超过 O ( log ⁡ n ) O(\log n) O(logn) 条连续的链,每条链上的点深度互不相同(即是自底向上的一条链,链上所有点的 LCA 为链的一个端点)。

重链剖分还能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息。

如:

  1. 修改 树上两点之间的路径上 所有点的值。
  2. 查询 树上两点之间的路径上 节点权值的 和/极值/其它(在序列上可以用数据结构维护,便于合并的信息)

除了配合数据结构来维护树上路径信息,树剖还可以用来 O ( log ⁡ n ) O(\log n) O(logn)(且常数较小)地求 LCA。在某些题目中,还可以利用其性质来灵活地运用树剖。

重链剖分

我们给出一些定义:

定义 重子节点 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。

定义 轻子节点 表示剩余的所有子结点。

从这个结点到重子节点的边为 重边

到其他轻子节点的边为 轻边

若干条首尾衔接的重边构成 重链

把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。

如图:

在这里插入图片描述

实现

树剖的实现分两个 DFS 的过程。代码如下:

第一个 DFS 记录每个结点的父节点(fa)、深度(dep)、子树大小(sz)、重子节点(hs)。

// 第一遍DFS,子树大小,重儿子,父亲,深度
void dfs1(int u,int f)
{
	sz[u]=1;
	hs[u]=-1;
	fa[u]=f;
	dep[u]=dep[f]+1;
	for(auto v : e[u])
	{
		if(v==f) continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(hs[u] == -1 || sz[hs[u]] < sz[v]) hs[u]=v;
	}
}

第二个 DFS 记录所在链的链顶(top,应初始化为结点本身)、重边优先遍历时的 DFS 序(id)、DFS 序对应的节点编号()。

// 第二遍DFS,每一个点DFS序,重链上的链头的元素
void dfs2(int u,int t)
{
	l[u]=++tot;
	top[u]=t;
	id[tot]=u;
	if(hs[u]!=-1)
	{
		dfs2(hs[u],t);
	}
	for(auto v : e[u])
	{
		if(v!=hs[u]&&v!=fa[u])
		{
			dfs2(v,v);
		}
	}
	r[u]=tot;
}

以下为代码实现。

我们先给出一些定义:

  • f a ( x ) fa(x) fa(x) 表示节点 x x x 在树上的父亲。
  • d e p ( x ) dep(x) dep(x) 表示节点 x x x 在树上的深度。
  • s i z ( x ) siz(x) siz(x) 表示节点 x x x 的子树的节点个数。
  • h s ( x ) hs(x) hs(x) 表示节点 x x x重儿子
  • t o p ( x ) top(x) top(x) 表示节点 x x x 所在 重链 的顶部节点(深度最小)。
  • d f n ( x ) dfn(x) dfn(x) 表示节点 x x xDFS 序,也是其在树中的编号。

我们进行两遍 DFS 预处理出这些值,其中第一次 DFS 求出 f a ( x ) fa(x) fa(x) d e p ( x ) dep(x) dep(x) s i z ( x ) siz(x) siz(x) s o n ( x ) son(x) son(x),第二次 DFS 求出 t o p ( x ) top(x) top(x) d f n ( x ) dfn(x) dfn(x) r n k ( x ) rnk(x) rnk(x)

重链剖分的性质

树上每个节点都属于且仅属于一条重链

重链开头的结点不一定是重子节点(因为重边是对于每一个结点都有定义的)。

所有的重链将整棵树 完全剖分

在剖分时 重边优先遍历,最后树的 DFS 序上,重链内的 DFS 序是连续的。按 DFN 排序后的序列即为剖分后的链。

一颗子树内的 DFS 序是连续的。

可以发现,当我们向下经过一条 轻边 时,所在子树的大小至少会除以二。

因此,对于树上的任意一条路径,把它拆分成从 LCA 分别向两边往下走,分别最多走 O ( log ⁡ n ) O(\log n) O(logn) 次,因此,树上的每条路径都可以被拆分成不超过 O ( log ⁡ n ) O(\log n) O(logn) 条重链。

常见应用

路径上维护

用树链剖分求树上两点路径权值和,伪代码如下:

TREE-PATH-SUM  ( u , v ) 1 t o t ← 0 2 while   u . t o p  is not  v . t o p 3 if   u . t o p . d e e p < v . t o p . d e e p 4 SWAP ( u , v ) 5 t o t ← t o t + sum of values between  u  and  u . t o p 6 u ← u . t o p . f a t h e r 7 t o t ← t o t + sum of values between  u  and  v 8 return   t o t \begin{array}{l} \text{TREE-PATH-SUM }(u,v) \\ \begin{array}{ll} 1 & tot\gets 0 \\ 2 & \textbf{while }u.top\text{ is not }v.top \\ 3 & \qquad \textbf{if }u.top.deep< v.top.deep \\ 4 & \qquad \qquad \text{SWAP}(u, v) \\ 5 & \qquad tot\gets tot + \text{sum of values between }u\text{ and }u.top \\ 6 & \qquad u\gets u.top.father \\ 7 & tot\gets tot + \text{sum of values between }u\text{ and }v \\ 8 & \textbf{return } tot \end{array} \end{array} TREE-PATH-SUM (u,v)12345678tot0while u.top is not v.topif u.top.deep<v.top.deepSWAP(u,v)tottot+sum of values between u and u.topuu.top.fathertottot+sum of values between u and vreturn tot

链上的 DFS 序是连续的,可以使用线段树、树状数组维护。

每次选择深度较大的链往上跳,直到两点在同一条链上。

同样的跳链结构适用于维护、统计路径上的其他信息。

子树维护

有时会要求,维护子树上的信息,譬如将以 x x x 为根的子树的所有结点的权值增加 v v v

在 DFS 搜索的时候,子树中的结点的 DFS 序是连续的。

每一个结点记录 bottom 表示所在子树连续区间末端的结点。

这样就把子树信息转化为连续的一段区间信息。

求最近公共祖先

P3379 【模板】最近公共祖先(LCA)

不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为 LCA。

向上跳重链时需要先跳所在重链顶端深度较大的那个。

int LCA(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]] < dep[top[v]]) v=fa[top[v]];
		else u=fa[top[u]];
	}
	if(dep[u]<dep[v]) return u;
	else return v;
}

参考代码:

#include <bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;

const int N = 5e5 + 3;

using i64 = long long;
int fa[N],hs[N],sz[N],id[N];
int top[N],dep[N],tot,l[N],r[N];
int n,m,s;
vector<int> e[N];
// 第一遍DFS,子树大小,重儿子,父亲,深度
void dfs1(int u,int f)
{
	sz[u]=1;
	hs[u]=-1;
	fa[u]=f;
	dep[u]=dep[f]+1;
	for(auto v : e[u])
	{
		if(v==f) continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(hs[u] == -1 || sz[hs[u]] < sz[v]) hs[u]=v;
	}
}
// 第二遍DFS,每一个点DFS序,重链上的链头的元素
void dfs2(int u,int t)
{
	l[u]=++tot;
	top[u]=t;
	id[tot]=u;
	if(hs[u]!=-1)
	{
		dfs2(hs[u],t);
	}
	for(auto v : e[u])
	{
		if(v!=hs[u]&&v!=fa[u])
		{
			dfs2(v,v);
		}
	}
	r[u]=tot;
}
int LCA(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]] < dep[top[v]]) v=fa[top[v]];
		else u=fa[top[u]];
	}
	if(dep[u]<dep[v]) return u;
	else return v;
}
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>s;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(s,-1);
	dfs2(s,s);
	while(m--)
	{
		int a,b;
		cin>>a>>b;
		cout<<LCA(a,b)<<endl;
	}
}



例题

「ZJOI2008」树的统计

题目大意

对一棵有 n n n 个节点,节点带权值的静态树,进行三种操作共 q q q 次:

  1. 修改单个节点的权值;
  2. 查询 u u u v v v 的路径上的最大权值;
  3. 查询 u u u v v v 的路径上的权值之和。

保证 1 ≤ n ≤ 30000 1\le n\le 30000 1n30000 0 ≤ q ≤ 200000 0\le q\le 200000 0q200000

解法

根据题面以及以上的性质,你的线段树需要维护三种操作:

  1. 单点修改;
  2. 区间查询最大值;
  3. 区间查询和。

单点修改很容易实现。

由于子树的 DFS 序连续(无论是否树剖都是如此),修改一个节点的子树只用修改这一段连续的 DFS 序区间。

问题是如何修改/查询两个节点之间的路径。

考虑我们是如何用 倍增法求解 LCA 的。首先我们 将两个节点提到同一高度,然后将两个节点一起向上跳。对于树链剖分也可以使用这样的思想。

在向上跳的过程中,如果当前节点在重链上,向上跳到重链顶端,如果当前节点不在重链上,向上跳一个节点。如此直到两节点相同。沿途更新/查询区间信息。

对于每个询问,最多经过 O ( log ⁡ n ) O(\log n) O(logn) 条重链,每条重链上线段树的复杂度为 O ( log ⁡ n ) O(\log n) O(logn),因此总时间复杂度为 O ( n log ⁡ n + q log ⁡ 2 n ) O(n\log n+q\log^2 n) O(nlogn+qlog2n)。实际上重链个数很难达到 O ( log ⁡ n ) O(\log n) O(logn)(可以用完全二叉树卡满),所以树剖在一般情况下常数较小。

给出一种代码实现:

#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
const int N=2e5+3;
using i64 = long long;
int n,q;
int l[N],r[N],id[N],dep[N],hs[N],sz[N];
int fa[N],top[N],a[N],tot;
vector<int> e[N];
struct info
{
    int maxn,sums;
}tr[N*4];
void update(int p)
{
    tr[p].maxn=max(tr[2*p].maxn,tr[2*p+1].maxn);
    tr[p].sums=tr[2*p].sums+tr[2*p+1].sums;
}
info operator+(const info& a,const info& b)
{
    return (info){max(a.maxn,b.maxn),a.sums+b.sums};
}
void build(int p,int l,int r)
{
    if(l==r)
    {
        tr[p].maxn=a[id[l]],tr[p].sums=a[id[l]];
        return ;
    }
    int mid=(l+r)/2;
    build(2*p,l,mid);
    build(2*p+1,mid+1,r);
    update(p);
}
void modify(int p,int l,int r,int pos,int val)
{
    if(l==r)
    {
        tr[p].maxn=val,tr[p].sums=val;
        return ;
    }
    //cout<<p<<endl;
    int mid=(l+r)/2;
    if(pos<=mid) modify(2*p,l,mid,pos,val); 
    else modify(2*p+1,mid+1,r,pos,val);
    update(p);
}
info query(int p,int l,int r,int ql,int qr)
{
    //cout<<p<<endl;
    if(ql==l&&qr==r)
    {
        return tr[p];
    }
    int mid=(l+r)/2;
    if(qr<=mid) return query(2*p,l,mid,ql,qr);
    else if(ql>mid) return query(2*p+1,mid+1,r,ql,qr);
    else return query(2*p,l,mid,ql,mid)+query(2*p+1,mid+1,r,mid+1,qr);
}
void dfs1(int u,int f)
{
    sz[u]=1;
    hs[u]=-1;
    fa[u]=f;
    dep[u]=dep[f]+1;
    for(auto v: e[u])
    {
        if(v==f) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(hs[u] == -1 || sz[hs[u]] < sz[v]) hs[u]=v;
    }
    //cout<<u<<endl;
}
void dfs2(int u,int t)
{
    l[u]=++tot;
    id[tot]=u;
    top[u]=t;
    if(hs[u]!=-1) dfs2(hs[u],t);
    for(auto v : e[u])
    {
        if(v==hs[u]||v==fa[u]) continue;
        dfs2(v,v);
    }
    r[u]=tot;
}
info check(int u,int v)
{
    info ans={(int)-1e9,0};
    while(top[u]!=top[v])
    {
        if(dep[top[u]]>dep[top[v]])
        {
            ans=ans+query(1,1,n,l[top[u]],l[u]);
            u=fa[top[u]];
        }
        else
        {
            ans=ans+query(1,1,n,l[top[v]],l[v]);
            v=fa[top[v]];
        }
        //cout<<v<<endl;
    }
    //cout<<"111"<<endl;
    if(dep[u]>dep[v]) ans=ans+query(1,1,n,l[v],l[u]);
    else ans=ans+query(1,1,n,l[u],l[v]);
    return ans;
}
void solve()
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for(int i=1;i<=n;i++) cin>>a[i];
    cin>>q;
    dfs1(1,0);
    dfs2(1,1);
    build(1,1,n);
    //cout<<"ddddd"<<endl;
    while(q--)
    {
        string op;
        cin>>op;
        if(op[0]=='C')
        {
            int u,t;
            cin>>u>>t;
            modify(1,1,n,l[u],t);
        }
        else
        {
            int u,v;
            cin>>u>>v;
            info ans=check(u,v);
            if(op=="QMAX") cout<<ans.maxn<<endl;
            else cout<<ans.sums<<endl;
        }
    }

}
signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T;
    T=1;
    //cin>>T;
    while(T--)
     {
         solve();
     }
     return 0;
} 

「SDOI2011」染色

题目大意

给定一棵 n n n 个节点的无根树,共有 m m m 个操作,操作分为两种:

  1. 将节点 a a a 到节点 b b b 的路径上的所有点(包括 a a a b b b)都染成颜色 c c c
  2. 询问节点 a a a 到节点 b b b 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:112221

对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 1 0 5 1 \leq n, m \leq 10^5 1n,m105 1 ≤ w i , c ≤ 1 0 9 1 \leq w_i, c \leq 10^9 1wi,c109 1 ≤ a , b , u , v ≤ n 1 \leq a, b, u, v \leq n 1a,b,u,vn o p op op 一定为 CQ,保证给出的图是一棵树。

解法

根据题面以及以上的性质,你的线段树需要维护三种操作:

  1. 左边颜色;
  2. 右边颜色;
  3. 颜色段数量。
代码:
#include<bits/stdc++.h>
#define endl "\n"
#define int long long
using namespace std;
const int N=2e5+3;
using i64 = long long;
int n,q;
int l[N],r[N],id[N],dep[N],hs[N],sz[N];
int fa[N],top[N],a[N],tot;
vector<int> e[N];
struct info
{
   int lc,rc,seg;
};
struct node
{
    info val;
    int tag;
}tr[N*4];
info operator+(const info& a,const info& b)
{
    info ans={0,0,0};
    ans={a.lc,b.rc,a.seg+b.seg-(a.rc==b.lc)};
    return ans;
}
void update(int p)
{
    tr[p].val=tr[2*p].val+tr[2*p+1].val;
}
void settag(int p,int t)
{
    tr[p].tag=t;
    tr[p].val={t,t,1};
}
void pushdown(int p)
{
    if(tr[p].tag!=0)
    {
        int t=tr[p].tag;
        settag(2*p,t);
        settag(2*p+1,t);
        tr[p].tag=0;
    }
}
void build(int p,int l,int r)
{
    if(l==r)
    {
        tr[p].val={a[id[l]],a[id[l]],1};
        tr[p].tag=0;
        return ;
    }
    int mid=(l+r)/2;
    build(2*p,l,mid);
    build(2*p+1,mid+1,r);
    update(p);
}
void modify(int p,int l,int r,int ql,int qr,int tag)
{
    if(ql==l&&qr==r)
    {
        settag(p,tag);
        return ;
    }
    pushdown(p);
    int mid=(l+r)/2;
    if(qr<=mid) modify(2*p,l,mid,ql,qr,tag); 
    else if(ql>mid) modify(2*p+1,mid+1,r,ql,qr,tag);
    else{
        modify(2*p,l,mid,ql,mid,tag);
        modify(2*p+1,mid+1,r,mid+1,qr,tag);
    }
    update(p);
}
info query(int p,int l,int r,int ql,int qr)
{
    //cout<<p<<endl;
    if(ql==l&&qr==r)
    {
        return tr[p].val;
    }
    pushdown(p);
    int mid=(l+r)/2;
    if(qr<=mid) return query(2*p,l,mid,ql,qr);
    else if(ql>mid) return query(2*p+1,mid+1,r,ql,qr);
    else return query(2*p,l,mid,ql,mid)+query(2*p+1,mid+1,r,mid+1,qr);
}
int query(int u,int v)
{
    info ansu={0,0,0},ansv={0,0,0};
    while(top[u]!=top[v])
    {
        if(dep[top[u]]>dep[top[v]])
        {
            ansu=query(1,1,n,l[top[u]],l[u])+ansu;
            u=fa[top[u]];
        }
        else
        {
            ansv=query(1,1,n,l[top[v]],l[v])+ansv;
            v=fa[top[v]];
        }
    }
    if(dep[u]>dep[v]) ansu=query(1,1,n,l[v],l[u])+ansu;
    else ansv=query(1,1,n,l[u],l[v])+ansv;
    int res=ansu.seg+ansv.seg-(ansu.lc==ansv.lc);
    return res;
}
void modify(int u,int v,int w)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]>dep[top[v]])
        {
            modify(1,1,n,l[top[u]],l[u],w);
            u=fa[top[u]];
        }
        else
        {
            modify(1,1,n,l[top[v]],l[v],w);
            v=fa[top[v]];
        }
    }
    if(dep[u]>dep[v]) modify(1,1,n,l[v],l[u],w);
    else modify(1,1,n,l[u],l[v],w);
}
void dfs1(int u,int f)
{
    sz[u]=1;
    hs[u]=-1;
    fa[u]=f;
    dep[u]=dep[f]+1;
    for(auto v: e[u])
    {
        if(v==f) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(hs[u] == -1 || sz[hs[u]] < sz[v]) hs[u]=v;
    }
    //cout<<u<<endl;
}
void dfs2(int u,int t)
{
    l[u]=++tot;
    id[tot]=u;
    top[u]=t;
    if(hs[u]!=-1) dfs2(hs[u],t);
    for(auto v : e[u])
    {
        if(v==hs[u]||v==fa[u]) continue;
        dfs2(v,v);
    }
    r[u]=tot;
}
void solve()
{
    cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,1);
    build(1,1,n);
    //cout<<"ddddd"<<endl;
    while(q--)
    {
        string op;
        cin>>op;
        if(op[0]=='C')
        {
            int u,v,w;
            cin>>u>>v>>w;
            modify(u,v,w);
        }
        else
        {
            int u,v;
            cin>>u>>v;
            int ans=query(u,v);
            cout<<ans<<endl;
        }
    }

}
signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T;
    T=1;
    //cin>>T;
    while(T--)
     {
         solve();
     }
     return 0;
} 

练习

「洛谷 P3379」【模板】最近公共祖先(LCA)(树剖求 LCA 无需数据结构,可以用作练习)

「JLOI2014」松鼠的新家(当然也可以用树上差分)

「HAOI2015」树上操作

「洛谷 P3384」【模板】重链剖分/树链剖分

「NOI2015」软件包管理器

「SDOI2011」染色

「SDOI2014」旅行

「POI2014」Hotel 加强版(长链剖分优化 DP)

攻略(长链剖分优化贪心)

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

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

相关文章

优化|计算合作博弈的成本分摊

原文&#xff1a; Caprara, A., & Letchford, A. N. (2010). New techniques for cost sharing in combinatorial optimization games. Mathematical programming, 124, 93-118. https://doi.org/10.1007/s10107-010-0357-7. 原文作者&#xff1a; Alberto Caprara, Adam N…

【js原型和原型链】

js原型和原型链 一、构造函数和原型对象中的this二、原型对象的constructor属性三、原型链四、关系图五、普通函数和函数对象 参考文章链接: link 一、构造函数和原型对象中的this 指向实例对象 // 定义构造函数function Star(name,age){this.name name;this.age age;conso…

5.10 飞行控制——自稳飞行

文章目录 5.10 飞行控制——自稳飞行5.10.1 数学模型——三轴角度系统&#xff08;1&#xff09;三轴角度系统微分方程&#xff08;2&#xff09;状态空间方程的建立 5.10.2 A1软件设计5.10.3 A1运行与调试5.10.4 三轴角度串级PID控制器5.10.5 A2软件设计5.10.6 A2运行与调试 总…

记录一个iOS工程添加文件的问题

遇到一个紧急问题&#xff0c;将工程copy了一份&#xff0c;然后需要将copy工程的一个文件夹 拖到现有的工程里面&#xff0c;由于事情紧急&#xff0c;就直接从工程目录中拖拽文件夹&#xff0c; 如下图 拖过之后&#xff0c;本地项目能跑了&#xff0c;但是远端自动化构建是…

World of Warcraft [CLASSIC][80][Grandel] Equipment Recovery

World of Warcraft [CLASSIC][80][Grandel] Equipment Recovery 魔兽世界怀旧服装备恢复流程 打开战网-服务-误删恢复 选择角色 恢复装备 选中自己不小心卖点的装备&#xff0c;例如我是2024.07.01卖掉T3肩膀 联系方式&#xff0c;描述&#xff0c;误操作时间 客服邮件 邮件中…

【性能优化】:探索系统瓶颈的根源(一)

背景 本次分享的这个项目是财务系统&#xff0c;众所周知&#xff0c;财务项目的特点是系统复杂、业务繁琐&#xff0c;开发一个完整的财务平台&#xff0c;开发周期长&#xff0c;参与人员多&#xff0c;且每月月初需要产出财务报表以供财务人员分析审核。 所以&#xff0c;财…

【大模型从入门到精通43】LLM部署运维(LLM Ops)使用Kubeflow Pipelines掌握LLM工作流5

这里写目录标题 实践练习 实践练习 设置Kubeflow Pipelines SDK # 导入Kubeflow Pipelines SDK中的必要模块 from kfp import dsl, compiler# 抑制来自Kubeflow Pipelines SDK的FutureWarning警告 import warnings warnings.filterwarnings("ignore", categoryFutu…

网络路由介绍,route指令,查询路由表的过程,默认路由

目录 路由 本地主机的路由功能 引入 route指令 查询路由表的过程 介绍 示例 默认路由 注意 路由 本地主机的路由功能 引入 报文经过多个路由器转发至公网,再从公网定位后转发至私网,最终到达目标主机 而报文肯定是要先经过本地主机的 所以本地主机也具有路由功能,也…

不平衡分类阈值移动的简单介绍

不平衡分类阈值移动的简单介绍 分类预测模型通常涉及预测类别标签。 尽管如此&#xff0c;许多机器学习算法能够预测类别成员的概率或得分&#xff0c;并且必须对其进行解释&#xff0c;然后才能将其映射到明确的类别标签。这是通过使用阈值&#xff08;例如 0.5&#xff09;…

redis面试(二十三)写锁释放

先加了写锁&#xff0c;后面再次加写锁或者读锁 anyLock: { “mode”: “write”, “UUID_01:threadId_01:write”: 2, “UUID_01:threadId_01”: 1 } 写锁的释放lua脚本在这里 RedissonWriteLock.unlockInnerAsync() 比如说现在的参数是这 KEYS[1] anyLock KEYS[2] redi…

计算机毕业设计选题推荐-摇滚音乐鉴赏网站-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

提示工程自动化实践

提示工程很糟糕。 这是使用大型语言模型最乏味的部分。这些模型非常挑剔&#xff0c;对提示进行看似无害的更改可能会导致截然不同的结果。我厌倦了手动调整、不系统的变化以及与手动提示工程相关的头痛…… 首先让我们统一认识&#xff0c;提示工程是指对 AI 模型给出的指令…

【jave】第一个JAVA程序,显示日期

<html> <script> function displayDate() { document.getElementById("demo").innerHTMLDate() } </script> <body> <p iddemo>这是学习的第一个程序 </p> <button typebutton οnclick"displayDate()">…

RabbitMQ练习(Publish/Subscribe)

1、RabbitMQ教程 《RabbitMQ Tutorials》https://www.rabbitmq.com/tutorials 2、环境准备 参考&#xff1a;《RabbitMQ练习&#xff08;Hello World&#xff09;》和《RabbitMQ练习&#xff08;Work Queues&#xff09;》。 确保RabbitMQ、Sender、Receiver、Receiver2容器…

数据仓库系列8:如何设计一个高性能的数据仓库模型?

目录 为什么高性能数据仓库模型如此重要?设计高性能数据仓库模型的核心原则案例研究&#xff1a;电子商务数据仓库设计步骤1: 需求分析步骤2: 选择适当的模型步骤3: 定义事实表和维度表步骤4: 设计星型模式 实施星型模式&#xff1a;步骤和最佳实践优化查询性能的关键技术数据…

【C语言】函数(一)

函数的概念 数学中我们其实就见过函数的概念&#xff0c;比如&#xff1a;一次函数 ykxb &#xff0c;k和b都是常数&#xff0c;给一个任意的x&#xff0c;就得到一个y值。 其实在C语言也引入函数&#xff08;function&#xff09;的概念&#xff0c;有些翻译为&#xff1a;子…

【uni-app】从零到一的项目搭建及环境配置

文章目录 简介环境配置Node环境配置安装 HBuilderX 开始创建项目项目结构开发指南插件管理运行项目调试测试发布 简介 uni-app 是一个使用 Vue.js 开发跨平台应用的框架&#xff0c;允许开发者编写一次代码&#xff0c;发布到 iOS、Android、Web&#xff08;包括 PC 和移动端浏…

【网络编程通关之路】 Tcp 基础回显服务器(Java实现)及保姆式知识原理详解 ! ! !

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

QtCreator错误:Qt没有被正确安装,请运行make install(适用Qt4、Qt5、Qt6)

一、问题环境 &#xff08;1&#xff09;Windows 10企业版&#xff0c;64位 &#xff08;2&#xff09;Visual Studio 2019 &#xff08;3&#xff09;Qt5.12.12 x64版本&#xff08;自己编译&#xff09; &#xff08;4&#xff09;Qt Creator 12.0.1 二、问题描述&#…

CM工作室发展史 上

&#xff0c;注&#xff1a;本文章未使用"无标题技术" 目录 &#xff08;超长文章&#xff01;&#xff09; 新手时期 初来乍到 第一篇文章 第一个专栏——沙雕程序 学习"块引用" 第一次修改用户名 学习"代码" "头文件风波"时期 头…