前言

如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。### 动态树概述

一、适用问题

动态树主要用于解决操作中带有加边、删边、换根的一系列问题,即树结构发生变化的问题,理论上来说,树链剖分的问题都能用 LCTLCTLCT 进行解决。

二、函数解析

LCTLCTLCT 本质是上对树进行实链剖分,实链剖分的意思就是将一棵树分成多条链,链中的边称为实边,链与链之间的边则称为虚边,每条链都是一个 splaysplaysplay,在 splaysplaysplay 中进行中序遍历即可还原原来的树结构。而 LCTLCTLCT 就是不断进行虚边、实边转换的一个算法。

LCTLCTLCT 中一共有 clearclearclear、pushUppushUppushUp、pushDownpushDownpushDown、updateupdateupdate、rotaterotaterotate、splaysplaysplay、accessaccessaccess、makeRootmakeRootmakeRoot、linklinklink、cutcutcut、findfindfind、splitsplitsplit 等函数,下面大致介绍一下每个函数的具体作用以及一些坑点,更多的是提纲挈领的作用,想要从最基础的地方开始学的话,推荐 oiwiki。

简单函数(仅操作单个 splaysplaysplay 的函数)

  1. clear(x)clear(x)clear(x):清除一个点的信息,如父亲、左右儿子、标记、维护信息等信息。
  2. pushUp(x)pushUp(x)pushUp(x):由左右儿子的信息更新父节点的信息,与线段树的 pushUp()pushUp()pushUp() 函数没有太大差别。
  3. pushDown(x)pushDown(x)pushDown(x):将当前节点的标记下放到儿子节点,如加、减、翻转等标记。
  4. update(x)update(x)update(x):一直递归到根节点,然后把标记信息不断下放,没有涉及任何虚实边的转换。
 void update(int p){ //递归地从上到下pushDown信息if(!isRoot(p)) update(f[p]);pushDown(p);}
  1. rotate(x)rotate(x)rotate(x):将当前节点向上旋转一层,可以自己模拟一下。此处改变了 splaysplaysplay 的内部结构,即子节点发生了改变,因此需要进行 pushUppushUppushUp,但是仍然没有进行任何虚实边的转换。
 inline void rotate(int x){ //将x向上旋转一层的操作int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}
  1. splay(x)splay(x)splay(x):将当前点旋转到 splaysplaysplay 的根节点,splaysplaysplay 到根节点作用在于不需要在向上进行更新。比如你现在要修改 xxx 的点权,但是每个节点还要维护子树 sumsumsum 的信息,如果 xxx 不是其所在 splaysplaysplay 的根节点,那么修改 xxx 的点权势必影响到其祖先节点的 sumsumsum 信息,因此需要将 xxx 旋转为其所在 splaysplaysplay 的根后再进行单点修改。注意 splaysplaysplay 函数也没有进行实边和虚边的转换。
 inline void splay(int x){ //把x旋转到当前splay的根update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}

以上函数都属于 LCTLCTLCT 函数中的简单函数,因为这些函数都只是在单个 splaysplaysplay 中进行操作,不涉及任何虚实边的转换。

复杂函数(涉及多个 splaysplaysplay 的操作,进行虚实边转换)

  1. access(x)access(x)access(x):将点 xxx 到根的路径经过的点放入同一个 splaysplaysplay 中,且这个 splaysplaysplay 中仅包含从 xxx 到根路径上经过的点。具体操作即是将 xxx 点不断转成其所在 splaysplaysplay 的根,然后再进行虚实边转换一直到根。此处 accessaccessaccess 函数有返回值,返回值为最后构成的 splaysplaysplay 的根节点。
inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根int p; //每次改变右儿子的值,因为整棵树是中序遍历,放入右儿子才能保证先遍历父亲再遍历儿子for(p = 0; x; p = x, x = f[x]){splay(x), ch[x][1] = p, pushUp(x);}return p;
}
  1. makeRoot(x)makeRoot(x)makeRoot(x):换根操作,将点 xxx 变成当前树的根。具体过程为先 accessaccessaccess 点 xxx,然后再将点 xxx 旋转为其 splaysplaysplay 所在根,然后将所有节点的左右儿子翻转即可。
inline void makeRoot(int p){ //使x点成为整棵树的根access(p); splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;
}
  1. split(x,y)split(x,y)split(x,y):从树中拎出 x→yx\rightarrow yx→y 的路径,返回该路径的 splaysplaysplay 根节点,可以查询路径最大值、点权和、边权和等信息。
inline int split(int x,int y){makeRoot(x); return access(y);
}
  1. find(x)find(x)find(x):即返回点 xxx 所在树的根节点,不是所在 splaysplaysplay 中的根节点,用于判断两点是否连通。
 inline int find(int p){ //找到x所在树的根节点编号access(p), splay(p);while(ls) pushDown(p), p = ls;return p;}
  1. link(x,y)link(x,y)link(x,y):连接树中 xxx、yyy 两点之间的边,无边变虚边,如果题目中没有保证操作一定合法,则需要自行判断 xxx、yyy 是否已经连通。
inline void link(int x,int y){ //在x、y两点间连一条边,连接了虚边if (find(x) != find(y)) makeRoot(x), f[x] = y;}
  1. cut(x,y)cut(x,y)cut(x,y):断开树中 xxx、yyy 两点之间的实边,两个点同时断开即可。
inline void cut(int x,int p){ //把x、y两点间边删掉,此处删除的是实边,注意实边和虚边的区别makeRoot(x), access(p), splay(p);if (ls == x && !rs) ls = f[x] = 0;}

三、具体细节

单点修改

由于 LCTLCTLCT 中维护了多个 splaysplaysplay,因此单点修改需要把该点修改的信息不断上传,所以我们需要先将点 xxx 旋转到 splaysplaysplay 的根或者整棵树的根,然后再进行单点修改。

如果题中只需要维护实链信息,则只需要旋转到 splaysplaysplay 的根,如果需要同时维护实链和虚链信息,即整棵子树的信息的话,则需要令该点为树根,即调用 makeRoot()makeRoot()makeRoot() 函数。

维护边权

由于 LCTLCTLCT 是不断地进行虚边、实边转换,因此没有固定的边结构,所以直接维护边权十分困难,因此我们将边转成点,边 (a,b)(a,b)(a,b) 成为一个点 xxx,link(a,x)link(a,x)link(a,x)、link(x,b)link(x,b)link(x,b) 即可。

维护子树信息

普通 LCTLCTLCT 只能维护具有可减性的子树信息,比如子树大小,子树贡献等,而子树 maxmaxmax、minminmin 等问题则不具有可减性,难以维护。

维护子树信息主要在于维护实边信息和虚边信息,而进行实虚转换的函数只有 makeRoot()makeRoot()makeRoot()、access()access()access()、link()link()link() 三个函数,只需要在该三个函数进行一定的修改即可,下面习题中包含了该问题可供参考。


动态树习题

1. [国家集训队] Tree II(模板题)

题意: nnn 个点一棵树,支持四种操作。(1≤n,q≤105,0≤c≤104)(1\leq n,q\leq 10^5,0\leq c\leq 10^4)(1≤n,q≤105,0≤c≤104)

思路: 三个涉及到路径的操作,都是先把 uuu 变成树根,然后 access(v)access(v)access(v),即拉起一条 uuu 到 vvv 的路径,使得 uuu 到 vvv 路径上的点都在一个 splaysplaysplay 中,然后获得这个 splaysplaysplay 的根节点,即可对根节点打标记完成。

删边则是令 uuu 为根,拉起 uuu 到 vvv 的路径,将 vvv 旋转成 splaysplaysplay 的根,然后儿子与父亲双向断开联系。加边则是令 uuu 为根,然后使 uuu 的父亲变成 vvv。

总结: 这题应该算是 LCTLCTLCT 的模板题,涉及的操作都是基础操作,没有太多思维上的难点。

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
#define int long long
const int N = 100010;
const int mod = 51061;
int n, q, u, v, c;
char op;struct LCT{#define ls ch[p][0]#define rs ch[p][1]#define Get(p) (ch[f[p]][1] == p)int ch[N][2], f[N], sum[N], val[N], siz[N], rev[N], add[N], mul[N];inline void clear(int p){ //清除这个点的信息ch[p][0] = ch[p][1] = f[p] = siz[p] = val[p] = sum[p] = rev[p] = add[p] = 0;mul[p] = 1;}inline int isRoot(int p){clear(0);return ch[f[p]][0] != p && ch[f[p]][1] != p;}inline void pushUp(int p){clear(0);siz[p] = (siz[ls] + 1 + siz[rs]) % mod;sum[p] = (sum[ls] + val[p] + sum[rs]) % mod;}inline void pushDown(int p){clear(0);if(mul[p] != 1){ //乘法if(ls){ //左儿子mul[ls] = (mul[ls] * mul[p]) % mod;val[ls] = (val[ls] * mul[p]) % mod;sum[ls] = (sum[ls] * mul[p]) % mod;add[ls] = (add[ls] * mul[p]) % mod;}if(rs){ //右儿子mul[rs] = (mul[rs] * mul[p]) % mod;val[rs] = (val[rs] * mul[p]) % mod;sum[rs] = (sum[rs] * mul[p]) % mod;add[rs] = (add[rs] * mul[p]) % mod;}mul[p] = 1;}if(add[p]){if(ls){add[ls] = (add[ls] + add[p]) % mod;val[ls] = (val[ls] + add[p]) % mod;sum[ls] = (sum[ls] + add[p] * siz[ls] % mod) % mod;}if(rs){add[rs] = (add[rs] + add[p]) % mod;val[rs] = (val[rs] + add[p]) % mod;sum[rs] = (sum[rs] + add[p] * siz[rs] % mod) % mod;}add[p] = 0;}if(rev[p]){ if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);rev[p] = 0;}}void update(int p){ //递归地从上到下pushDown信息//没有将实边变成虚边if(!isRoot(p)) update(f[p]);pushDown(p);}inline void rotate(int x){ //将x向上旋转一层的操作//没有将实边变成虚边int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}inline void splay(int x){ //把x旋转到当前splay的根//没有将实边变成虚边update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根//进行了边的虚实变换int p; //每次改变右儿子的值,因为整棵树是中序遍历,放入右儿子才能保证先遍历父亲再遍历儿子for(p = 0; x; p = x, x = f[x]){splay(x), ch[x][1] = p, pushUp(x);}return p;}inline void makeRoot(int p){ //使x点成为整棵树的根access(p); splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;}inline void link(int x,int y){ //在x、y两点间连一条边,连接了虚边if (find(x) != find(y)) makeRoot(x), f[x] = y;}inline void cut(int x,int p){ //把x、y两点间边删掉,此处删除的是实边,注意实边和虚边的区别makeRoot(x), access(p), splay(p);if (ls == x && !rs) ls = f[x] = 0;}inline int find(int p){ //找到x所在树的根节点编号access(p), splay(p);while(ls) pushDown(p), p = ls;return p;}//中序遍历即可还原树结构void print(int p){if(!p) return;pushDown(p);print(ls);printf("%lld ",p);print(rs);}
}st;signed main() {scanf("%lld%lld", &n, &q);for (int i = 1; i <= n; i++) st.val[i] = 1;for (int i = 1; i < n; i++) {scanf("%lld%lld", &u, &v);st.link(u,v);}while (q--) {scanf(" %c%lld%lld", &op, &u, &v);if (op == '+') { //+ u v c, u到v的路径上的点权值+cscanf("%lld", &c);//u变成树根,拉起v到u的链,把v旋到splay的根st.makeRoot(u); v = st.access(v);st.val[v] = (st.val[v] + c) % mod;st.sum[v] = (st.sum[v] + st.siz[v] * c % mod) % mod;st.add[v] = (st.add[v] + c) % mod;}if (op == '-') { //- u1 v1 u2 v2, 删除(u1,v1), 加上(u2,v2)st.cut(u,v);scanf("%lld%lld", &u, &v);st.link(u,v);}if (op == '*') { //* u v c, u到v的路径乘上cscanf("%lld", &c);st.makeRoot(u); v = st.access(v);st.val[v] = st.val[v] * c % mod;st.sum[v] = st.sum[v] * c % mod;st.mul[v] = st.mul[v] * c % mod;}if (op == '/'){ //u v, 询问u到v路径权值和st.makeRoot(u); v = st.access(v);printf("%lld\n", st.sum[v]);}}return 0;
}
2. Query on a tree

题意: nnn 个点一棵树,支持两种操作。(1≤n,q≤104)(1\leq n,q\leq 10^4)(1≤n,q≤104)

思路: 边权 LCTLCTLCT,需要对于每一条边建一个节点,即树中一共有 2∗n−12*n-12∗n−1 个节点,每个边节点与上下两个节点连边。

建边需要先确定每个节点的边权之后再进行 linklinklink,因为点修改会对该节点的祖先节点产生影响,需要将该点旋至 splaysplaysplay 端点后才能进行修改。

总结: 总结一下构建 LCTLCTLCT 构建的关键,构建 LCTLCTLCT 需要先对各个顶点赋值然后再进行 linklinklink 操作,若是 linklinklink 之后再赋值相当于点修改,而点修改需要将点旋为 splaysplaysplay 根之后才能进行更改。

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 20000+10;
int n,val[N];struct LCT{#define ls ch[p][0]#define rs ch[p][1]#define Get(p) (ch[f[p]][1] == p)int ch[N][2], f[N], maxn[N], val[N], siz[N], rev[N];inline void clear(int p){ //清除这个点的信息ch[p][0] = ch[p][1] = f[p] = siz[p] = val[p] = maxn[p] = 0;}inline int isRoot(int p){clear(0);return ch[f[p]][0] != p && ch[f[p]][1] != p;}inline void pushUp(int p){clear(0);siz[p] = siz[ls] + 1 + siz[rs];maxn[p] = max(val[p],max(maxn[ls],maxn[rs]));}inline void pushDown(int p){clear(0);if(rev[p]){ if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);rev[p] = 0;}}void update(int p){ //递归地从上到下pushDown信息if(!isRoot(p)) update(f[p]);pushDown(p);}inline void rotate(int x){ //将x向上旋转一层的操作int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}inline void splay(int x){ //把x旋转到当前splay的根update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根int p;for(p = 0; x; p = x, x = f[x]){splay(x), ch[x][1] = p, pushUp(x);}return p;}inline void makeRoot(int p){ //使x点成为整棵树的根access(p); splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;}inline void link(int x,int y){ //在x、y两点间连一条边makeRoot(x), f[x] = y; //dfs建树, 每条边都是有效的, 因此不需要判断是否有效}
}st;int main(){int _; scanf("%d",&_);while(_--){scanf("%d",&n);rep(i,0,2*n) st.clear(i);rep(i,1,n-1){int a,b,c; scanf("%d%d%d",&a,&b,&c);st.val[i+n] = c;st.link(a,i+n);st.link(i+n,b);}while(1){char s[20]; scanf("%s",s);if(s[0] == 'D') break;else if(s[0] == 'C'){int a,b; scanf("%d%d",&a,&b);st.splay(a+n); //先转为splay根节点st.val[a+n] = b;}else{int a,b; scanf("%d%d",&a,&b);st.makeRoot(a);b = st.access(b);printf("%d\n",st.maxn[b]);}}}return 0;
}
3. Can you answer these queries VII

题意: nnn 个点一棵树,每个节点有一个值,支持两种操作。(1≤n,q≤105)(1\leq n,q\leq 10^5)(1≤n,q≤105)

思路: 对每一个点维护一个 lc[i]lc[i]lc[i]、rc[i]rc[i]rc[i]、maxn[i]maxn[i]maxn[i] 表示点 iii 子树中左连续的最大值、右连续的最大值以及整棵子树中的最大连续值。

需要注意一点,交换左右儿子的时候,还需要把每个节点的 lclclc 和 rcrcrc 进行交换,其余细节见代码。

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const ll inf = 1e9+100;
const int N = 1e5+10;
int n,Q;struct LCT{#define ls ch[p][0]#define rs ch[p][1]#define Get(p) (ch[f[p]][1] == p)int ch[N][2], f[N];ll maxn[N], sum[N], lc[N], rc[N], val[N], siz[N], lazy[N];bool rev[N];inline void clear(int p){ //清除这个点的信息ch[p][0] = ch[p][1] = f[p] = val[p] = maxn[p] = lc[p] = sum[p] = rc[p] = siz[p] = 0;}inline int isRoot(int p){return ch[f[p]][0] != p && ch[f[p]][1] != p;}inline void pushUp(int p){siz[p] = siz[ls] + 1 + siz[rs];sum[p] = val[p] + sum[ls] + sum[rs]; //ls、rs可能为0maxn[p] = max(maxn[ls],max(maxn[rs],rc[ls]+lc[rs]+val[p]));lc[p] = max(lc[ls],sum[ls]+val[p]+lc[rs]);rc[p] = max(rc[rs],sum[rs]+val[p]+rc[ls]);}inline void pushDown(int p){if(rev[p]){ if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);swap(lc[ls],rc[ls]); swap(lc[rs],rc[rs]); //交换左右儿子时还要交换左右连续最大值rev[p] = 0;}if(lazy[p] != -inf){if(ls){sum[ls] = siz[ls]*lazy[p]; val[ls] = lazy[ls] = lazy[p];lc[ls] = rc[ls] = maxn[ls] = lazy[p] > 0 ? sum[ls]:0;}if(rs){sum[rs] = siz[rs]*lazy[p]; val[rs] = lazy[rs] = lazy[p];lc[rs] = rc[rs] = maxn[rs] = lazy[p] > 0 ? sum[rs]:0;}lazy[p] = -inf; }}void update(int p){ //递归地从上到下pushDown信息if(!isRoot(p)) update(f[p]);pushDown(p);}inline void rotate(int x){ //将x向上旋转一层的操作int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}inline void splay(int x){ //把x旋转到当前splay的根update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根int p;for(p = 0; x; p = x, x = f[x]){splay(x), ch[x][1] = p, pushUp(x);}return p;}inline void makeRoot(int p){ //使x点成为整棵树的根access(p);splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;}inline void link(int x,int y){ //在x、y两点间连一条边makeRoot(x), f[x] = y; //dfs建树, 每条边都是有效的, 因此不需要判断是否有效}
}st;int main(){scanf("%d",&n);rep(i,1,n){scanf("%lld",&st.val[i]);st.siz[i] = 1; st.sum[i] = st.val[i];st.lazy[i] = -inf;st.lc[i] = st.rc[i] = st.maxn[i] = st.val[i] > 0 ? st.val[i]:0;}rep(i,1,n-1){int a,b; scanf("%d%d",&a,&b);st.link(a,b);}scanf("%d",&Q);while(Q--){ int op; scanf("%d",&op);if(op == 1){ //a->b maxint a,b; scanf("%d%d",&a,&b);st.makeRoot(a);b = st.access(b);printf("%lld\n",st.maxn[b]);}else{ //a->b to cint a,b; ll c; scanf("%d%d%lld",&a,&b,&c);st.makeRoot(a);b = st.access(b);st.val[b] = st.lazy[b] = c;st.sum[b] = st.siz[b]*c;st.lc[b] = st.rc[b] = st.maxn[b] = c > 0 ? st.sum[b]:0;}}return 0;
}
4. [ZJOI2008] 树的统计 Count

题意: 一棵树上有 nnn 个节点,每个节点都有一个权值 www。支持三种操作:CHANGECHANGECHANGE uuu ttt:把结点 uuu 的权值改为 ttt;QMAXQMAXQMAX uuu vvv:询问从点 uuu 到点 vvv 的路径上的节点的最大权值;QSUMQSUMQSUM uuu vvv:询问从点 uuu 到点 vvv 的路径上的节点的权值和。(1≤n≤3∗104,1≤q≤2∗105)(1\leq n\leq 3*10^4,1\leq q\leq 2*10^5)(1≤n≤3∗104,1≤q≤2∗105)

思路: 单点查询 + 路径最大值 + 路径 sumsumsum 和,非常裸的题目,纯当练习。

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const int inf = 1e9+100;
const int N = 40000+10;
int n,Q,A[N],B[N];struct LCT{#define ls ch[p][0]#define rs ch[p][1]#define Get(p) (ch[f[p]][1] == p)int ch[N][2], f[N];int maxn[N], sum[N], val[N];bool rev[N];inline void clear(int p){ //清除这个点的信息ch[p][0] = ch[p][1] = f[p] = val[p] = maxn[p] = 0;}inline int isRoot(int p){return ch[f[p]][0] != p && ch[f[p]][1] != p;}inline void pushUp(int p){sum[p] = val[p] + sum[ls] + sum[rs]; //ls、rs可能为0maxn[p] = max(val[p],max(maxn[ls],maxn[rs]));}inline void pushDown(int p){if(rev[p]){ if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);rev[p] = 0;}}void update(int p){ //递归地从上到下pushDown信息if(!isRoot(p)) update(f[p]);pushDown(p);}inline void rotate(int x){ //将x向上旋转一层的操作int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}inline void splay(int x){ //把x旋转到当前splay的根update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根int p;for(p = 0; x; p = x, x = f[x]){splay(x), ch[x][1] = p, pushUp(x);}return p;}inline void makeRoot(int p){ //使x点成为整棵树的根access(p);splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;}inline void link(int x,int y){ //在x、y两点间连一条边makeRoot(x), f[x] = y; //dfs建树, 每条边都是有效的, 因此不需要判断是否有效}
}st;int main(){scanf("%d",&n);st.maxn[0] = -inf;rep(i,1,n-1) scanf("%d%d",&A[i],&B[i]);rep(i,1,n){int hp; scanf("%d",&hp);st.val[i] = st.maxn[i] = st.sum[i] = hp;}rep(i,1,n-1) st.link(A[i],B[i]);scanf("%d",&Q);while(Q--){ char s[20]; int u,v;scanf("%s%d%d",s,&u,&v);if(s[0] == 'C'){st.splay(u);st.val[u] = v;st.pushUp(u);}else if(s[1] == 'M'){st.makeRoot(u);v = st.access(v);printf("%d\n",st.maxn[v]);}else{st.makeRoot(u);v = st.access(v);printf("%d\n",st.sum[v]);}}return 0;
}
5. 最小差值生成树

题意: nnn 个点,mmm 条边的一个无向图,求边权最大值与最小值的差值最小的生成树。(1≤n≤5∗104,1≤m≤2∗105)(1\leq n\leq 5*10^4,1\leq m\leq 2*10^5)(1≤n≤5∗104,1≤m≤2∗105)

思路: 关于这类特殊生成树问题,一般考虑用 LCTLCTLCT 动态维护树结构然后更新答案。

此题也可以这样考虑。将边按边权从小到大排序,如果 (a,b)(a,b)(a,b) 两点不连通,则加上该边,如果 (a,b)(a,b)(a,b) 两点连通,则将 a→ba\rightarrow ba→b 路径上边权最小的边去除,然后连上当前的边。维护过程不断更新最大值与最小值的差值,不断取 minminmin 即可。

因此只需要维护一个边权 LCTLCTLCT,并且维护路径最小值以及最小值点的编号,然后动态加边删边即可。还需要对在树中的边打上标记,去除的时候删去标记,用于查找整棵树中的最小边权。

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 2e5+5e4+100;
const int M = 2e5+10;
int n, m, vis[N], pos = 1, num, ans = 1e9;
struct Edge{int a,b,w;bool operator < (Edge xx) const {return w < xx.w;}
}e[N];void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}struct LCT{#define ls ch[p][0]#define rs ch[p][1]#define Get(p) (ch[f[p]][1] == p)int ch[N][2], f[N], val[N], minn[N], mpos[N], rev[N];inline void clear(int p){ //清除这个点的信息ch[p][0] = ch[p][1] = f[p] = val[p] = mpos[p] = minn[p] = 0;}inline int isRoot(int p){return ch[f[p]][0] != p && ch[f[p]][1] != p;}inline void pushUp(int p){minn[p] = val[p]; mpos[p] = p;if(ls && minn[ls] < minn[p]) minn[p] = minn[ls], mpos[p] = mpos[ls];if(rs && minn[rs] < minn[p]) minn[p] = minn[rs], mpos[p] = mpos[rs];}inline void pushDown(int p){if(rev[p]){ if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);rev[p] = 0;}}void update(int p){ //递归地从上到下pushDown信息if(!isRoot(p)) update(f[p]);pushDown(p);}inline void rotate(int x){ //将x向上旋转一层的操作int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}inline void splay(int x){ //把x旋转到当前splay的根update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根int p = 0;for(p = 0; x; p = x, x = f[x]){splay(x), ch[x][1] = p, pushUp(x);}return p;}inline void makeRoot(int p){ //使x点成为整棵树的根access(p); splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;}inline void link(int x,int y){ //在x、y两点间连一条边// if (find(x) != find(y)) makeRoot(x), f[x] = y;}inline void cut(int x,int p){ //把x、y两点间边删掉makeRoot(x), access(p), splay(p);if (ls == x && !rs) ls = f[x] = 0;}inline int find(int p){ //找到x所在树的根节点编号access(p), splay(p);while(ls) pushDown(p), p = ls;return p;}
}st;signed main() {scanf("%d%d", &n, &m);rep(i,0,n) st.val[i] = st.minn[i] = 1e5;rep(i,1,m) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w);sort(e+1,e+1+m);rep(i,1,m) st.val[i+n] = st.minn[i+n] = e[i].w, st.mpos[i+n] = i+n;rep(i,1,m){if(e[i].a == e[i].b) continue;if(st.find(e[i].a) != st.find(e[i].b)){st.link(e[i].a,i+n); st.link(i+n,e[i].b);num++; vis[i] = 1;while(!vis[pos]) pos++;if(num == n-1) ans = min(ans,e[i].w-e[pos].w);}else{st.makeRoot(e[i].a);int p1 = st.access(e[i].b);p1 = st.mpos[p1];st.cut(e[p1-n].a,p1); st.cut(p1,e[p1-n].b); vis[p1-n] = 0;st.link(e[i].a,i+n); st.link(i+n,e[i].b); vis[i] = 1;while(!vis[pos]) pos++;if(num == n-1) ans = min(ans,e[i].w-e[pos].w);}}printf("%d\n",ans);return 0;
}
6. [BJOI2014] 大融合

题意: nnn 个点,一共 qqq 次操作。一共有两种操作类型,AxyA \ x \ yA x y 表示连通 (x,y)(x,y)(x,y),保证操作合法,且始终是棵森林。QxyQ\ x\ yQ x y 表示查询去除 (x,y)(x,y)(x,y) 边之后,xxx 所在树的节点数 ∗*∗ yyy 所在树的节点数。(1≤n,q≤105)(1\leq n,q\leq 10^5)(1≤n,q≤105)

思路: 我们一般遇到的都是维护链上节点个数的问题,而此题要求这颗树上的节点个数,因此我们需要同时维护虚边和实边的信息。

我们令 sz[x]sz[x]sz[x] 表示节点 xxx 子树中节点个数,sz2[x]sz2[x]sz2[x] 表示节点 xxx 虚儿子的节点个数和。因此 sz[x]=sz[ls]+sz[rs]+1+sz2[x]sz[x]=sz[ls]+sz[rs]+1+sz2[x]sz[x]=sz[ls]+sz[rs]+1+sz2[x],而这也正是 pushUppushUppushUp 函数。

因此我们只需要维护 sz2[x]sz2[x]sz2[x] 即可,然后观察哪些函数会改变 sz2[x]sz2[x]sz2[x] 的值,不难发现,只有 makeRootmakeRootmakeRoot、accessaccessaccess、linklinklink、cutcutcut 会改变边的虚实关系,其中 makeRootmakeRootmakeRoot 主要修改在于调用了 accessaccessaccess 函数,而 cutcutcut 只是删除实边不会修改虚边,因此真正关键的函数即为 linklinklink 和 accessaccessaccess 函数,具体操作见代码,不难思考。

这里主要讲解 linklinklink 中修改 sz2[y]sz2[y]sz2[y] 信息时为什么需要将节点 yyy makeRoot(y)makeRoot(y)makeRoot(y),原因在于单点修改之后,其祖先节点维护的信息都会发生变化,因此一般的问题需要 splay(y)splay(y)splay(y),因为一般问题只需要维护实链信息。然后在该问题中还维护了虚链信息,因此需要 makeRoot(y)makeRoot(y)makeRoot(y) 而不是 splay(y)splay(y)splay(y)。

代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
const int N = 100010;
int n, q;struct LCT{#define ls ch[p][0]#define rs ch[p][1]#define Get(p) (ch[f[p]][1] == p)int ch[N][2], f[N], siz[N], siz2[N], rev[N];inline void clear(int p){ //清除这个点的信息ch[p][0] = ch[p][1] = f[p] = siz[p] = siz2[p] = rev[p] = 0;}inline int isRoot(int p){clear(0);return ch[f[p]][0] != p && ch[f[p]][1] != p;}inline void pushUp(int p){clear(0);siz[p] = siz[ls] + 1 + siz[rs] + siz2[p];}inline void pushDown(int p){clear(0);if(rev[p]){ if(ls) rev[ls] ^= 1, swap(ch[ls][0],ch[ls][1]);if(rs) rev[rs] ^= 1, swap(ch[rs][0],ch[rs][1]);rev[p] = 0;}}void update(int p){ //递归地从上到下pushDown信息if(!isRoot(p)) update(f[p]);pushDown(p);}inline void rotate(int x){ //将x向上旋转一层的操作int y = f[x], z = f[y], k = Get(x);if(!isRoot(y)) ch[z][ch[z][1] == y] = x;ch[y][k] = ch[x][!k], f[ch[y][k]] = y;ch[x][!k] = y, f[y] = x, f[x] = z;pushUp(y); //要先pushUp(y)pushUp(x);}inline void splay(int x){ //把x旋转到当前splay的根update(x); //将上面的标记完全下放for(int fa; fa = f[x], !isRoot(x); rotate(x)){if(!isRoot(fa)) rotate(Get(fa) == Get(x) ? fa : x);}}inline int access(int x){ //把从根到x的所有点放在一条实链里, 返回这个splay的根int p; //每次改变右儿子的值,因为整棵树是中序遍历,放入右儿子才能保证先遍历父亲再遍历儿子for(p = 0; x; p = x, x = f[x]){splay(x), siz2[x] += siz[ch[x][1]]-siz[p], ch[x][1] = p, pushUp(x);}return p;}inline void makeRoot(int p){ //使x点成为整棵树的根access(p); splay(p);swap(ch[p][0],ch[p][1]); //把整条链反向rev[p] ^= 1;}inline void link(int x,int y){ //在x、y两点间连一条边//makeRoot(x)的作用是使得x无父亲//makeRoot(y)的作用是使得y无父亲,因此可以修改y的信息,不用去更新y的祖先makeRoot(x), makeRoot(y), f[x] = y, siz2[y] += siz[x]; pushUp(y);}inline void cut(int x,int p){ //把x、y两点间边删掉,此处删除的是实边,注意实边和虚边的区别makeRoot(x), access(p), splay(p);if (ls == x && !rs) ls = f[x] = 0;}inline int find(int p){ //找到x所在树的根节点编号access(p), splay(p);while(ls) pushDown(p), p = ls;return p;}
}st;signed main() {scanf("%d%d", &n, &q);rep(i,1,n) st.siz[i] = 1;while (q--) {char op[10]; int x,y; scanf("%s%d%d",op,&x,&y);if(op[0] == 'A') st.link(x,y);else{st.cut(x,y);st.makeRoot(x); st.splay(x);int a1 = st.siz[x];st.makeRoot(y); st.splay(y);int a2 = st.siz[y];st.link(x,y);printf("%lld\n",(ll)a1*(ll)a2);}}return 0;
}
7. Sone1

题意: 支持 121212 种操作,包括链 maxmaxmax、minminmin、sumsumsum,子树 maxmaxmax、minminmin、sumsumsum,换根,换边,子树和链的修改与加值。(1≤n,m≤105)(1\leq n,m\leq 10^5)(1≤n,m≤105)

思路: TopTreeTopTreeTopTree 典型例题,主要思路是对于每个点维护了一个 splaysplaysplay,详情看 clarisclarisclaris 的题解

代码:
贴上 clairsclairsclairs 的代码。

/*
Toptree即为可以维护子树信息的lct升级版
*/
#include<cstdio>
#define N 200010
const int inf=~0U>>1;
inline void swap(int&a,int&b){int c=a;a=b;b=c;}
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void read(int&a){char c;bool f=0;a=0;while(!((((c=getchar())>='0')&&(c<='9'))||(c=='-')));if(c!='-')a=c-'0';else f=1;while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';if(f)a=-a;
}
struct tag{int a,b;//ax+btag(){a=1,b=0;}tag(int x,int y){a=x,b=y;}inline bool ex(){return a!=1||b;}inline tag operator+(const tag&x){return tag(a*x.a,b*x.a+x.b);}
};
inline int atag(int x,tag y){return x*y.a+y.b;}
struct data{int sum,minv,maxv,size;data(){sum=size=0,minv=inf,maxv=-inf;}data(int x){sum=minv=maxv=x,size=1;}data(int a,int b,int c,int d){sum=a,minv=b,maxv=c,size=d;}inline data operator+(const data&x){return data(sum+x.sum,min(minv,x.minv),max(maxv,x.maxv),size+x.size);}
};
inline data operator+(const data&a,const tag&b){return a.size?data(a.sum*b.a+a.size*b.b,atag(a.minv,b),atag(a.maxv,b),a.size):a;}
//son:0-1:重链儿子,2-3:AAA树儿子
int f[N],son[N][4],a[N],tot,rt,rub,ru[N];bool rev[N],in[N];
int val[N];
data csum[N],tsum[N],asum[N];
tag ctag[N],ttag[N];
inline bool isroot(int x,int t){if(t)return !f[x]||!in[f[x]]||!in[x];return !f[x]||(son[f[x]][0]!=x&&son[f[x]][1]!=x)||in[f[x]]||in[x];
}
inline void rev1(int x){if(!x)return;swap(son[x][0],son[x][1]);rev[x]^=1;
}
inline void tagchain(int x,tag p){if(!x)return;csum[x]=csum[x]+p;asum[x]=csum[x]+tsum[x];val[x]=atag(val[x],p);ctag[x]=ctag[x]+p;
}
inline void tagtree(int x,tag p,bool t){if(!x)return;tsum[x]=tsum[x]+p;ttag[x]=ttag[x]+p;if(!in[x]&&t)tagchain(x,p);else asum[x]=csum[x]+tsum[x];
}
inline void pb(int x){if(!x)return;if(rev[x])rev1(son[x][0]),rev1(son[x][1]),rev[x]=0;if(!in[x]&&ctag[x].ex())tagchain(son[x][0],ctag[x]),tagchain(son[x][1],ctag[x]),ctag[x]=tag();if(ttag[x].ex()){tagtree(son[x][0],ttag[x],0),tagtree(son[x][1],ttag[x],0);tagtree(son[x][2],ttag[x],1),tagtree(son[x][3],ttag[x],1);ttag[x]=tag();}
}
inline void up(int x){tsum[x]=data();for(int i=0;i<2;i++)if(son[x][i])tsum[x]=tsum[x]+tsum[son[x][i]];for(int i=2;i<4;i++)if(son[x][i])tsum[x]=tsum[x]+asum[son[x][i]];if(in[x]){csum[x]=data();asum[x]=tsum[x];}else{csum[x]=data(val[x]);for(int i=0;i<2;i++)if(son[x][i])csum[x]=csum[x]+csum[son[x][i]];asum[x]=csum[x]+tsum[x];}
}
inline int child(int x,int t){pb(son[x][t]);return son[x][t];}
inline void rotate(int x,int t){int y=f[x],w=(son[y][t+1]==x)+t;son[y][w]=son[x][w^1];if(son[x][w^1])f[son[x][w^1]]=y;if(f[y])for(int z=f[y],i=0;i<4;i++)if(son[z][i]==y)son[z][i]=x;f[x]=f[y];f[y]=x;son[x][w^1]=y;up(y);
}
inline void splay(int x,int t=0){int s=1,i=x,y;a[1]=i;while(!isroot(i,t))a[++s]=i=f[i];while(s)pb(a[s--]);while(!isroot(x,t)){y=f[x];if(!isroot(y,t)){if((son[f[y]][t]==y)^(son[y][t]==x))rotate(x,t);else rotate(y,t);}rotate(x,t);}up(x);
}
inline int newnode(){int x=rub?ru[rub--]:++tot;son[x][2]=son[x][3]=0;in[x]=1;return x;
}
inline void setson(int x,int t,int y){son[x][t]=y;f[y]=x;}
inline int pos(int x){for(int i=0;i<4;i++)if(son[f[x]][i]==x)return i;return 4;}
inline void add(int x,int y){//从x连出一条虚边到yif(!y)return;pb(x);for(int i=2;i<4;i++)if(!son[x][i]){setson(x,i,y);return;}while(son[x][2]&&in[son[x][2]])x=child(x,2);int z=newnode();setson(z,2,son[x][2]);setson(z,3,y);setson(x,2,z);splay(z,2);
}
inline void del(int x){//将x与其虚边上的父亲断开if(!x)return;splay(x);if(!f[x])return;int y=f[x];if(in[y]){int s=1,i=y,z=f[y];a[1]=i;while(!isroot(i,2))a[++s]=i=f[i];while(s)pb(a[s--]);if(z){setson(z,pos(y),child(y,pos(x)^1));splay(z,2);}ru[++rub]=y;}else{son[y][pos(x)]=0;splay(y);}f[x]=0;
}
inline int fa(int x){//x通过虚边的父亲splay(x);if(!f[x])return 0;if(!in[f[x]])return f[x];int t=f[x];splay(t,2);return f[t];
}
inline int access(int x){int y=0;for(;x;y=x,x=fa(x)){splay(x);del(y);add(x,son[x][1]);setson(x,1,y);up(x);}return y;
}
inline int lca(int x,int y){access(x);return access(y);
}
inline int root(int x){access(x);splay(x);while(son[x][0])x=son[x][0];return x;
}
inline void makeroot(int x){access(x);splay(x);rev1(x);
}
inline void link(int x,int y){makeroot(x);add(y,x);access(x);
}
inline void cut(int x){access(x);splay(x);f[son[x][0]]=0;son[x][0]=0;up(x);
}
inline void changechain(int x,int y,tag p){makeroot(x);access(y);splay(y);tagchain(y,p);
}
inline data askchain(int x,int y){makeroot(x);access(y);splay(y);return csum[y];
}
inline void changetree(int x,tag p){access(x);splay(x);val[x]=atag(val[x],p);for(int i=2;i<4;i++)if(son[x][i])tagtree(son[x][i],p,1);up(x);splay(x);
}
inline data asktree(int x){access(x);splay(x);data t=data(val[x]);for(int i=2;i<4;i++)if(son[x][i])t=t+asum[son[x][i]];return t;
}
int n,m,x,y,z,k,i,ed[N][2];
int main(){read(n);read(m);tot=n;for(i=1;i<n;i++)read(ed[i][0]),read(ed[i][1]); //连边for(i=1;i<=n;i++)read(val[i]),up(i); //先赋点权,再连边for(i=1;i<n;i++)link(ed[i][0],ed[i][1]); //每个点的权值read(rt); //给出根makeroot(rt);while(m--){read(k);if(k==1){//换根,x变成根read(rt);makeroot(rt);}if(k==9){//x的父亲变成y,x父亲换成yread(x),read(y);if(lca(x,y)==x)continue;cut(x);link(y,x);makeroot(rt);}if(k==0){//子树赋值,以x为根的子树点权值改为yread(x),read(y);changetree(x,tag(0,y));}if(k==5){//子树加,x为根子树点权值加上yread(x),read(y);changetree(x,tag(1,y));}if(k==3){//子树最小值,x为根子树中点权值求minread(x);printf("%d\n",asktree(x).minv);}if(k==4){//子树最大值,x为根子树中点权值求maxread(x);printf("%d\n",asktree(x).maxv);}if(k==11){//子树和,x为根子树中点权sumread(x);printf("%d\n",asktree(x).sum);}if(k==2){//链赋值,x-y路径上点权值改为zread(x),read(y),read(z);changechain(x,y,tag(0,z));makeroot(rt);}if(k==6){//链加,x-y路径上点权值加上zread(x),read(y),read(z);changechain(x,y,tag(1,z));makeroot(rt);}if(k==7){//链最小值,x-y路径上点权值求minread(x),read(y);printf("%d\n",askchain(x,y).minv);makeroot(rt);}if(k==8){//链最大值,x-y路径上点权值求maxread(x),read(y);printf("%d\n",askchain(x,y).maxv);makeroot(rt);}if(k==10){//链和,x-y路径上点权值求sumread(x),read(y);printf("%d\n",askchain(x,y).sum);makeroot(rt);}}return 0;
}

后记

本篇博客到这里就结束了,祝大家 ACACAC 愉快,一起爱上 LCTLCTLCT 把!(๑•̀ㅂ•́)و✧

动态树算法概述及习题相关推荐

  1. UML统一建模语言第1章 UML概述课后习题

    <UML2基础.建模与设计教程>杨弘平等编著,清华大学出版社,第1章 UML概述课后习题 1.UML事物有哪些? 构件事物.行为事物.分组事物.注释事物. 2.UML关系有哪些? 依赖.关 ...

  2. 动态域名解析概述及操作步骤讲解

    随着IPv4公网资源的紧缺,以及越来越多的互联网服务发展,许多用户都采取了动态域名解析的方法来解决内网穿透和服务器搭建问题.那么动态域名解析是什么?怎么操作呢?本文将详细介绍. 动态域名解析概述 现在 ...

  3. 软件工程 科学出版社 郑逢斌主编 第1章 软件工程概述 课后习题答案

    软件工程 科学出版社 郑逢斌 第1章 软件工程概述 课后习题答案 1. 简述软件的发展过程. 2. 简述软件的定义和特点. 定义: 在 IEEE 软件工程词汇标准中,软件的定义是:软件是计算机程序.规 ...

  4. 通信与计算机网络FSC,数据通信与计算机网络概述信道习题.pdf

    第一章.第二章习题 概述及信道与接口 1.目前使用最为普遍的局域网是( ). A. 以太网 B. 标记环网 C.FDDI 网 D.标记总线网 2.在 OSI 模型的七层结构中,能进行直接通信的是( ) ...

  5. 计算机网络--第一章 概述--课后习题答案

    计算机网络原理第五版课后的全部习题答案,学习通作业答案. 说明:计算机网络原理其他章节的习题答案也在此"计算机网络"专栏. 1-01 计算机网络向用户可以提供那些服务? 答: 连通 ...

  6. 软件工程教程:第1章软件工程概述 课后习题

    <软件工程教程>(第2版)主编:吴迪 马宏茹 丁万宁 电子科技大学出版社 目录 一.选择题 二.简答题 (1)什么是软件危机?软件危机表现在哪些方面? (2)简述软件和软件工程的定义以及软 ...

  7. HCIA-RS自用笔记(13)路由典型问题案例分析、动态路由概述

    路由典型问题案例分析(7/19,27min) Windows配置路由 示例:> route PRINT> route PRINT -4> route PRINT -6> rou ...

  8. 【计算机网络(微课版)】第1章 概述 课后习题及答案

    1-1计算机网络向用户可以提供哪些服务? 计算机网络可以向用户提供的最重要的功能有两个,即连通性和共享性.具体可以展开为提供:浏览信息和发布信息的平台:通信和交流的平台:休闲娱乐的平台:资源共享的平台 ...

  9. 计算机网络:第七章概述课后习题及答案(精细版)

    1. 计算机网络中的安全威胁都有哪些,需要哪些安全服务. 计算机网络所面临的安全威胁主要来自两大类攻击即被动攻击和主动攻击. 这两类攻击中四种最基本的形式是: (1) 截获(interception) ...

  10. 第一章 计算机网络概述[知识点+课后习题+练习题]

    概念和功能 21世纪一些重要特征:数字化.网络化.信息化,它是一个以网络为核心的信息时代. 三网融合:电信网络.有线电视网络.计算机网络 四网融合:电网.电信网络.有线电视网络.计算机网络 计算机网络 ...

最新文章

  1. 微信小程序把玩(二十八)image组件
  2. kalinux实现自适用全屏、与物理主机共享文件方法
  3. 数据结构实验之栈与队列一:进制转换
  4. 现代密码学5.3--Hash and MAC
  5. How to Fix “Username is not in the sudoers file. This incident will be reported” in Ubuntu
  6. html给文字添加阴影效果,text-shadow css文字阴影_字体投影属性样式
  7. HTTP协议实体的基本讲解
  8. create table as select性能测试
  9. c语言无符号扩展,C语言无符号和有符号的区别
  10. mysql 相关记录
  11. python的networkx 算法_python图算法库Networkx笔记 - Node and Centrality
  12. win的反义词_133个英语单词反义词,小学生必须要掌握!为孩子收藏吧
  13. Ajax与JavaWeb分页
  14. 独家:为了永不停机的计算服务 - 五月月刊 | 凌云时刻
  15. 系统中的obj文件、dll文件、so文件、lib文件、exe文件、vcproj文件、sln文件
  16. com.aliyun.openservices.ons.api.exception.ONSClientException: defaultMQProducer send order exception
  17. mac上的pdf编辑器怎么才能直接修改PDF文档上的字体大小
  18. UVM实战 卷I学习笔记10——UVM中的寄存器模型(3)
  19. 用鼠标点击画一个五边形
  20. zabbix自定义监控模板+grafana图形展示

热门文章

  1. 禁用安全模式小方法!!~
  2. 荒野行动为什么不能获取服务器信息,荒野行动获取服务器信息一直不动 服务器信息0解决方法...
  3. java输入学生名字输出_用java实现:输入学生个数,并输入每个学生的名字还有分数,结果输出分数最高和分数第二高的学生......
  4. python 3d绘图kmeans_使用python绘制3d的图形
  5. postgresql将数据从一个表内容插入到另一个表_关系型数据库管理系统openGauss 1.0.1版本发布...
  6. 计算机系统结构期末张晨曦,计算机系统结构(张晨曦)试题
  7. C++中的set(STL的应用部分)
  8. luis soares mysql,mysql访问报错如下
  9. POJ 2406题解kmp算法的应用
  10. android中横向滑动功能,Android开发基于ViewPager+GridView实现仿大众点评横向滑动功能...