作者:hsez_yyh

链接: FHQ-Treap——从入门到入坟_hsez_yyh的博客-CSDN博客

来源:湖北省黄石二中信息竞赛组
       著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

平衡树有很多种,其中fhq-treap 可以说是最强的一种平衡树之一,它可以维护值域,也可以维护下标,还能维护区间修改,更难能可贵的是,它可以完成splay都完成不了的可持久化操作。其唯一弱于splay的就是在LCT上,splay维护LCT的复杂度是O(nlog(n)),而fhq-treap的复杂度为O(nlog^2(n)),稍微大了一点,但是比splay好写多了。

其实FHQ-treap的实现非常简单,本人认为,数据结构其实都不难实现,只是难于在概念上抽象的理解,所以,对于学习数据结构的最好方法就是手动模拟,在开始我们愉快的FHQ-treap之旅之前,在此先瞻仰一下FHQ-treap的最先引入者范浩强大佬。

现在我们正式开始(其实迫于教练压力才写的这篇文章)

首先fhq-treap是一个二叉搜索树(BST),它的每个节点有两个主要信息:keyval,key是我们fhq-treap要维护的键值,而val是随机生成的(rand()),key信息主要用于我们对于题目信息的处理,而val则是用于维持fhq-treap在结构上满足期望高度为logn的,val保证了fhq-treap拥有稳定的logn的高度,不会像splay一样退化。fhq-treap 除了要满足关于key的BST性质之外,还需满足关于val的小根堆性质。就是说,对于任意fhq-treap中的节点 i ,其左子树上的所有节点的key小于等于 i 节点的key值,i 节点所有右子树上所有节点的key 大于等于 i 节点的key值;对于任意节点 i ,其左、右儿子的val值大于等于 i 的val值(满足小根堆的性质)。要牢牢记住这两点的区别,否则会像我一样搞混,然后写代码的时候狂 WA,(手动悲剧)。下面给出一颗fhq-treap:

上图就是一颗fhq-treap,其中每个节点中的数字为val,每个节点下方的数字为当前节点的key       可以发现一颗fhq-treap中序遍历后得到的序列是单调递增(如果维护方式不同其也可以是单调递减的)的。 非常好的性质——神犇ljz说。 这里,我们要引入fhq-treap的两个基本操作:splitmerge, 其中split是分离操作,merge是合并操作,下面我们来一一阐明:

1. split  通常情况下,split用于分离一颗fhq-treap,对于一个元素 x ,我们会将这棵fhq-treap分裂为左右两棵树,左树上每个节点的key值都小于等于(<=)x , 而右树上的所有节点的key值都大于 x ;下面给出代码:

void split(int pos,int xx,int &l,int &r)// pos是当前fhq-treap的根
{  // &l 和 &r分别是fhq-treap分裂后得到的两个fhq-treap的根,这里用指针(引用)的方式,便于传递值if(!pos)//如果该节点为空树,则分裂成的左右两个树也应为空{l=r=0;return ;}if(tr[pos].key<=xx)// 如果当前节点的key值小于等于xx,则证明该节点的左子树上所有的key值也小于等于xx{l=pos;//让该节点成为左树的节点split(tr[l].r,xx,tr[l].r,r);//继续递归分离当前节点的右子树}else//同上{r=pos;split(tr[r].l,xx,l,tr[r].l);//继续递归分离当前节点的左子树}push_up(pos);//一定要记得push_up,因为pos点的左右子树大小发生了改变,否则会导致信息不对
}
//以上便是递归分离左右子树的方法

以上代码如果学过线段树的话会好理解的多,本蒟蒻也建议先去熟练掌握线段树再来学习fhq-treap,这样会轻松很多。下面我们来模拟这个过程——本蒟蒻就是开始没有自己去模拟,导致一直不是很理解fhq-treap的实现方式。

对于上图,假如我们要将其分离为小于等于18和大于18两个部分:

在此之后,我们会继续递归,分裂K节点的左子树,大家可以自己手动模拟下(主要是画图太麻烦了,QAQ),由于I节点的权值20>18,所以我们将 I 节点归入右fhq-treap,接下来我们会碰到空节点,递归将会跳出,这时,我们就能得到左右两棵fhq-treap了:

以上就是fhq-treap的基本操作之一split操作的全部过程了 。

2. merge 操作  这是一个合并操作,但它也不能随便合并,如果要合并两棵fhq-treap,那么它们可以进行合并当且仅当左fhq-treap上所有节点的key值都小于等于右fhq-treap中的最小key值(也就是左树小于等于右树)或两棵树中有空树。 这个性质很重要,为根据val合并两棵树做好的前提,保证了BST结构不会因合并而被破坏。 合并时,我们会根据左右两棵树的根节点的val值大小进行合并,大家先看一下代码:

int merge(int l,int r)//传入左右子树的根节点
{if(!l||!r) //如果有空树return l|r;//直接返回非空树(本蒟蒻这样的写法等同于l+r,是用来装杯的)if(tr[l].val<=tr[r].val){//如果左树上当前节点的val值小于右树上当前节点的val值//将l变为合并后的新fhq-treap的根节点tr[l].r=merge(tr[l].r,r);//继续递归确定l的右子树该接谁push_up(l);//一定记得上传return l;//返回新得到的根节点}else// 同上{tr[r].l=merge(l,tr[r].l);push_up(r);return r;}
}

还是一样,画图好理解:

以上是两棵要合并的fhq-treap

以上就是fhq-treap合并的全部过程了。

到现在,fhq-treap的最基本也是精华所在你已经完全掌握了,下面我们来看一道题目:

【模板】普通平衡树 - 洛谷     这道题就是平衡树的板子题,用fhq-treap可以轻松切掉,代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,idx;
int dl,dr,temp,rt;
struct node
{int l,r;int key,val;int si;//以当前节点为根的树的节点数
}tr[N*2];
int get_rand(int xx)
{tr[++idx].key=xx;tr[idx].val=rand();tr[idx].si=1;return idx;
} //建立一个新节点
void push_up(int pos)
{tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;// 注意这里,这里跟线段树的push_up不一样,要记得加上自己的大小
}
void split(int pos,int xx,int &l,int &r)//分离操作
{if(!pos){l=r=0;return ;}if(tr[pos].key<=xx){l=pos;split(tr[l].r,xx,tr[l].r,r);}else{r=pos;split(tr[r].l,xx,l,tr[r].l);}push_up(pos);
}
int merge(int l,int r) // 合并操作
{if(!l||!r)return l|r;if(tr[l].val<=tr[r].val){tr[l].r=merge(tr[l].r,r);push_up(l);return l;}else{tr[r].l=merge(l,tr[r].l);push_up(r);return r;}
}
void insert(int xx)
{// 对于合并操作,先将原树分裂成<=xx-1和 >xx-1(>=xx)两个部分// 将xx与<=xx-1的部分合并,再和>xx-1的部分合并split(rt,xx-1,dl,dr);rt=merge(merge(dl,get_rand(xx)),dr);
}
void delet(int xx)
{split(rt,xx-1,dl,dr);split(dr,xx,temp,dr);//连续两次分裂,temp得到了一棵所有key值==xx的fhq-treaptemp=merge(tr[temp].l,tr[temp].r);//由于只能删一个xx,而原树中可能有很多xx,那么我们直接合并temp的左右子树即可rt=merge(dl,merge(temp,dr));// 将原树恢复(已删去xx)
}
int get_rk(int xx)
{
//非常简单,直接分离出<=xx-1的一棵树,也就是<xx的一棵树
//然后xx的排名就是<xx的树的大小+1即可split(rt,xx-1,dl,dr);int rnk=tr[dl].si+1;rt=merge(dl,dr);return rnk;
}
int get_num(int pos,int xx)
{
//这里采用递归搜索,思想见二分搜索树int u=tr[tr[pos].l].si+1;if(u==xx)return tr[pos].key;if(u>xx) return get_num(tr[pos].l,xx);else return get_num(tr[pos].r,xx-u);
}
int main()
{scanf("%d",&n);int op,xx;while(n--){scanf("%d %d",&op,&xx);if(op==1) insert(xx);else if(op==2) delet(xx);else if(op==3) printf("%d\n",get_rk(xx));else if(op==4) printf("%d\n",get_num(rt,xx));else if(op==5){split(rt,xx-1,dl,dr);printf("%d\n",get_num(dl,tr[dl].si));// 前驱就是<=xx-1的这棵树中排名最后的那个数rt=merge(dl,dr);}else {split(rt,xx,dl,dr);printf("%d\n",get_num(dr,1));//后继就是>xx的这棵树中排名第一的树    rt=merge(dl,dr);}}return 0;
}

好了,目前为止,平衡树维护值域的方法你就已经掌握了,下面我们再来看一个fhq-treap维护序列的题目:

【模板】文艺平衡树 - 洛谷 这道题就是典型的fhq-treap维护序列,操作也非常简单,仅仅是加上了一个翻转的懒标记而已——可见学线段树的必要。

值得一说的是,fhq-treap在维护序列时,void split(int pos,int k,int &l,int &r)的意思不再是分离<=k和>k的两棵树,而是变为了 将序列pos 的前k个元素分离出来形成一棵新fhq-treap l,另外的一部分形成另一颗 fhq-treap r .

下面展示代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
struct node
{int l,r;int key,val;int si;bool tag;
}tr[N*2];
int dl,dr,temp,rt,idx;
int get_rand(int xx)
{tr[++idx].key=xx;tr[idx].val=rand();tr[idx].si=1;return idx;
}
void push_up(int pos)
{tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;
}
void push_down(int pos)
{//下传懒标记,如果一直下传下去,将会实现左右区间翻转if(tr[pos].tag){tr[tr[pos].l].tag^=1;tr[tr[pos].r].tag^=1;swap(tr[pos].l,tr[pos].r);tr[pos].tag=0;}
}
void split(int pos,int k,int &l,int &r)
{if(!pos){l=r=0;return ;}//只要对某个节点的子节点做操作,就一定要将该节点的标记下传push_down(pos);int u=tr[tr[pos].l].si+1;if(u<=k){l=pos;split(tr[pos].r,k-u,tr[pos].r,r);push_up(l);}else{r=pos;split(tr[pos].l,k,l,tr[pos].l);push_up(r);}
}
int merge(int l,int r)
{if(!l||!r) return l|r;push_down(l);push_down(r);if(tr[l].val<=tr[r].val){tr[l].r=merge(tr[l].r,r);push_up(l);return l;}else{tr[r].l=merge(l,tr[r].l);push_up(r);return r;}
}
void reverse(int l,int r)
{int p1=0,p2=0,p3=0,p4=0;split(rt,l-1,p1,p2);split(p2,(r-l+1),p3,p4);// 将要翻转的一段分离出来,打上标记,再合并回去tr[p3].tag^=1;p2=merge(p3,p4);rt=merge(p1,p2);
}
void print_res(int pos)
{push_down(pos);if(tr[pos].l) print_res(tr[pos].l);printf("%d ",tr[pos].key);if(tr[pos].r) print_res(tr[pos].r);// 迭代输出,没什么好说的
}
int main()
{scanf("%d %d",&n,&m);for(int i=1;i<=n;i++){split(rt,i-1,dl,dr);rt=merge(merge(dl,get_rand(i)),dr);}int xx,yy;while(m--){scanf("%d %d",&xx,&yy);reverse(xx,yy);}print_res(rt);return 0;
}

到此,平衡树最大的两个功能都能实现了 Orz ! ! !

但既然要入坟,那么在此推荐两道题目:

266. 超级备忘录 - AcWing题库

[NOI2005] 维护数列 - 洛谷                祝大家WA的开心,本蒟蒻虽然过了,但是懒得贴代码了——都是泪

你以为就这样结束了??? 不,还远远不止,既然要入坟,那么就要坚持到底:

fhq-treap之所以是现在OI界地位逐渐超过splay的结构,自然有splay做不到的地方。众所周知 ,splay依靠二次旋转来保持logn的复杂度的,然而,这样做会经常改变树的结构,自然,splay对于处理可持久化的信息是一点办法都没有——就是嘛,转来转去的一看就不好  。但是嘛,splay可以做的fhq-treap可以做,splay不能做的fhq-treap还可以做(就算lct复杂度高一点,但是也是可以做的嘛,手动滑稽) 。

口胡完毕,进入正题: 细心的你可以发现,在fhq-treap的 split 操作和 merge 操作时,由于fhq-treap的期望高度是 logn,并且可以发现 split 和 merge 时都是自上而下的,那么,对于每次 split 和 merge 操作,真正有所变动的点只有 logn 个  ,所以我们只需要在操作过程中记录下沿途经过的 logn 个节点,就能对某次历史版本有所记录,其记录方式与主席树类似——又是线段树 。

如果再仔细思考,我们可以发现,每次 merge 操作都是合并先由 split 操作分离的两棵树,所以,对于真正结果有影响的操作只有 split ,所以,我们只需要在每次 split 的时候记录沿途的节点即可。

例题: 【模板】可持久化平衡树 - 洛谷

需要注意的是,我们每次操作产生的新版本,都要创建一个新根来指向次版本。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+10;
int n,dl,dr,temp;
struct node
{int l,r;LL key,val;LL si;
}tr[N*60];
int rt[N*60],idx;
void push_up(int pos)
{tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;}
int get_rand(int xx)
{tr[++idx].key=xx;tr[idx].val=rand();tr[idx].si=1;return idx;
}
void split(int pos,int xx,int &l,int &r)
{if(!pos){l=r=0;return ;}if(tr[pos].key<=xx){l=++idx;tr[l]=tr[pos];split(tr[l].r,xx,tr[l].r,r);push_up(l);}else{r=++idx;tr[r]=tr[pos];split(tr[pos].l,xx,l,tr[r].l);push_up(r);}
}
int merge(int l,int r)
{if(!l||!r) return l|r;int pos=++idx;if(tr[l].val<=tr[r].val){tr[pos]=tr[l];tr[pos].r=merge(tr[pos].r,r);}else{tr[pos]=tr[r];tr[pos].l=merge(l,tr[pos].l);}push_up(pos);return pos;
}
void insert(int &pos,int xx)
{split(pos,xx-1,dl,dr);pos=merge(merge(dl,get_rand(xx)),dr);
}
void delet(int &pos,int xx)
{split(pos,xx-1,dl,dr);split(dr,xx,temp,dr);temp=merge(tr[temp].l,tr[temp].r);pos=merge(dl,merge(temp,dr));
}
int get_rk(int &pos,int xx)
{split(pos,xx-1,dl,dr);int rk=tr[dl].si+1;pos=merge(dl,dr);return rk;
}
int get_num(int pos,int xx)
{int u=tr[tr[pos].l].si+1;if(u==xx) return tr[pos].key;if(u>xx) return get_num(tr[pos].l,xx);else return get_num(tr[pos].r,xx-u);
}
int main()
{scanf("%d",&n);int op,xx,t;int cnt=0;while(n--){cnt++;scanf("%d %d %d",&t,&op,&xx);rt[cnt]=rt[t];if(op==1) insert(rt[cnt],xx);else if(op==2) delet(rt[cnt],xx);else if(op==3) printf("%d\n",get_rk(rt[cnt],xx));else if(op==4) printf("%d\n",get_num(rt[cnt],xx));else if(op==5){split(rt[cnt],xx-1,dl,dr);printf("%d\n",get_num(dl,tr[dl].si));rt[cnt]=merge(dl,dr);}else{split(rt[cnt],xx,dl,dr);printf("%d\n",get_num(dr,1));rt[cnt]=merge(dl,dr);}}return 0;
}

既然有了这道题,那么你肯定能想到另外一道:【模板】可持久化文艺平衡树 - 洛谷

其实和普通的文艺平衡树一样,只需要多加一个可持久化操作即可,代码如下(也就写了一小时):

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int n,idx;
int dl,dr,temp;
struct node
{int l,r;int key,val;int si;bool rev;LL sum;
}tr[N*100];
int rt[N];
void push_up(int pos)
{tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;tr[pos].sum=tr[tr[pos].l].sum+tr[tr[pos].r].sum+tr[pos].key;
}
int get_rand(int xx)
{tr[++idx].sum=xx;tr[idx].key=xx;tr[idx].si=1;tr[idx].val=rand();return idx;
}
void push_down(int &pos)
{if(!tr[pos].rev) return ;if(tr[pos].l){tr[++idx]=tr[tr[pos].l];tr[pos].l=idx;tr[tr[pos].l].rev^=1;}if(tr[pos].r){tr[++idx]=tr[tr[pos].r];tr[pos].r=idx;tr[tr[pos].r].rev^=1;}tr[pos].rev=0;swap(tr[pos].l,tr[pos].r);
}
void split(int pos,int k,int &l,int &r)
{if(!pos){l=r=0;return ;}push_down(pos);int u=tr[tr[pos].l].si+1;if(u<=k){l=++idx;tr[l]=tr[pos];split(tr[pos].r,k-u,tr[l].r,r);push_up(l);}else{r=++idx;tr[r]=tr[pos];split(tr[pos].l,k,l,tr[r].l);push_up(r);}
}
int merge(int l,int r)
{if(!l||!r) return l|r;int pos=++idx;if(tr[l].val<=tr[r].val){tr[pos]=tr[l];push_down(pos);tr[pos].r=merge(tr[pos].r,r);push_up(pos);return pos;}else{tr[pos]=tr[r];push_down(pos);tr[pos].l=merge(l,tr[pos].l);push_up(pos);return pos;}
}
void insert(int &pos,int xx,int yy)
{split(pos,xx,dl,dr);dl=merge(dl,get_rand(yy));pos=merge(dl,dr);
}
void delet(int &pos,int xx)
{split(pos,xx-1,dl,dr);split(dr,1,temp,dr);pos=merge(dl,dr);
}
void reverse(int &pos,int l,int r)
{split(pos,l-1,dl,dr);split(dr,r-l+1,temp,dr);tr[temp].rev^=1;dr=merge(temp,dr);pos=merge(dl,dr);
}
LL query(int &pos,int l,int r)
{split(pos,l-1,dl,dr);split(dr,r-l+1,temp,dr);LL ans=tr[temp].sum;dr=merge(temp,dr);pos=merge(dl,dr);return ans;
}
int main()
{scanf("%d",&n);int op,v,xx,yy;LL lan=0;for(int t=1;t<=n;t++){scanf("%d %d",&v,&op);rt[t]=rt[v];if(op==2){scanf("%d",&xx);xx^=lan;delet(rt[t],xx);}else{scanf("%d %d",&xx,&yy);if(op==1){xx^=lan,yy^=lan;insert(rt[t],xx,yy);}else if(op==3){xx^=lan,yy^=lan;reverse(rt[t],xx,yy);}else{xx^=lan,yy^=lan;lan=query(rt[t],xx,yy);printf("%lld\n",lan);}}}return 0;
}

一定要注意的是,每次操作,不管是合并、分离还是push_down,只要影响到树形和树中节点的信息,就必须新建节点来储存,所以fhq-treap的可持久化格外的费空间,不过现在空间都给的足,所以一般都能过。用fhq-Treap支持区间翻转和可持久化,需要注意的是和区间修改主席树一样,在push_down翻转rev的时候需要复制左右儿子结点,把rev推到新复制的结点上。

多体会下,就能很容易理解啦,在本蒟蒻看来,其实平衡树比线段树简单多了 qwq

好了,现在已经快入坟了,就差轻轻一推了,那么它来啦!!! 作为平衡树五题的压轴题目,也是平衡树的另一大考点:【模板】二逼平衡树(树套树) - 洛谷         相信有人已经看不下去了,本蒟蒻也是到这里才体会到了平衡树的快乐 ,当本蒟蒻听到树套树之后,直接兴奋的半个小时交了一发,然后快乐的只过了一个点。在听完课后,虽然本蒟蒻会写了,但是,但是!,又交了一发,只过了90分(关键是就我第一次提交过的那个点没过——离离原上谱) ,虽然后来我看题解用树状数组套平衡树过了,However,这和我线段树套平衡树的初衷不相符,所以这里就不贴代码,就稍微解释一下吧(尬)。

对于此题,我们可以发现,如果单纯的求前驱和后继,排名,数值,用一棵fhq-treap来维护绰绰有余,然而,此题异常毒瘤,它让我们在一个区间中进行上述操作。区间? 这不又是线段树嘛,欸,猜对了。又题目的提示可以知道:我们要在一棵维护区间的线段树上做平衡树,也就是下标线段树套值域平衡树,线段树的每个节点都是一段区间,所以我们要在线段树的每个节点上开一个平衡树,这样就可以轻松地维护信息啦——但是,这样也会有问题,我们在查询排名为k的数时,由于答案需要多棵不可合并的平衡树一起提供,所以我们要在线段树二分区间(logn)的前提下在平衡树中二分答案(logn*logn),那么复杂度就会到达 log^3(n),这样又会被出题人卡死。怎么办呢,为了降低复杂度,我们可以利用线段树的二分性,把树套树改成 下标平衡树套值域线段树,这样复杂度就会将下来,具体实现推荐去看肖然大佬的blog 【树套树】 - 肖然 的博客 - 洛谷博客

本蒟蒻就水到这了,反正我是已经入坟躺平了,希望各位神犇大佬AC顺利 Orz

FHQ-Treap(非旋treap/平衡树)——从入门到入坟相关推荐

  1. 非旋treap套线段树

    BZOJ3065. 去年用pascal 块链过了.. 今年来试了试非旋treap大法   注定被块链完爆 代码留这. 第一份 :辣鸡的  垃圾回收做法  跑得极慢 1 #include <bit ...

  2. 从入门到入坟搭建FreeNAS服务器并配置NextCloud_NAS存储

    从入门到入坟搭建FreeNAS服务器并配置NextCloud 2021-01-06 23:15:38 173点赞 963收藏 100评论 创作立场声明:本文所有商品皆自费购入,第一次发表文章,若有不当 ...

  3. canvas 从入门到入坟

    文章目录 canvas 从入门到入坟 1. 概述 2. 使用场景 2.1 图表的绘制 2.2 canvas游戏 2.3 说明与作用 3. Canvas 入门 3.1 完成目标 3.2 初始化 3.3 ...

  4. 深聊MySQL,从入门到入坟之:MySQL竟然也有后悔药!!!

    MySQL后悔药,防止看铁窗 1.引言 2.5种后悔药 2.1 limit 2.2 先测试后生产 2.3 删除前,先备份 2.4 删除前,先查询 2.5 修改时, begin+commit 3. 总结 ...

  5. 【教程汇总+持续更新】Unity游戏开发从入门到入坟

    新的一年,本该在年前整理的年终总结被拖到了年后开工.去年大量时间投入在Catlike教程的翻译上,截止目前位置,教程的进度已经完全追平原作者. 去年还有一部分是断断续续的更新SLG实战教程,但遗憾的是 ...

  6. 初阶指针---从入门到入坟

    今天我们来见识一下c语言里让万千少年少女从入门到入坟的一道大门槛--指针 目录 1.指针是什么? 2.指针和指针类型 3.野指针 4. 指针运算 5. 指针和数组 6. 二级指针 7. 指针数组 1. ...

  7. [NOIP]2017列队——旋转treap/非旋转treap

    Sylvia 是一个热爱学习的女孩子.  前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia所在的方阵中有n × m名学生,方阵的行数为 n,列数为m.  为了便 ...

  8. P4847 银河英雄传说V2 非旋treap

    传送门 文章目录 题意: 思路: 题意: 思路: 让我们分析一下题目需要实现什么操作: (1)(1)(1)将某个序列放到某个的后面,也就是合并两个序列. (2)(2)(2)将一个序列从某处断开. (3 ...

  9. 【20181026T2】**图【最小瓶颈路+非旋Treap+启发式合并】

    题面 [错解] 最大最小?最小生成树嘛 蛤?还要求和? 点分治? 不可做啊 写了个MST+暴力LCA,30pts,140多行 事后发现30分是给dijkstra的 woc [正解] 树上计数问题:①并 ...

最新文章

  1. modelsim仿真正确FPGA运行不正确的可能原因 - cm4写寄存器错
  2. Space-Filling Designs
  3. NeHe OpenGL课程 网址整理
  4. 【C++深度剖析教程1】C++中的经典问题解析-c++中的对象的构造顺序与析构顺序
  5. 信息学奥赛一本通(1246:膨胀的木棍)
  6. 5 SD配置-企业结构-定义-定义销售组
  7. LintCode 547---两数组的交集
  8. 数据结构(严蔚敏)------双链循环线性表
  9. VS2010上连接SQLite数据库
  10. JDBC(实现增删改查的通用操作)
  11. mysql json字段的使用与意义
  12. Linux下配置安装JupyterNotebook,windows下通过浏览器直接连接使用
  13. Tensorflow2.0深度学习框架的搭建与环境配置
  14. 【odoo15】如何使用 python xmlrpc 连接 odoo
  15. OpenGL基础48:高度贴图(上)
  16. 如何在Linux上安装和使用TeamViewer
  17. 服务器2003系统安装office,简单3步处理office 2003安装出错
  18. SQL Server 2000个人版的安装
  19. 已更新或删除的行值要么不能使该行成为唯一行
  20. 高并发时,读文件、读SESSION、读缓存(Redis)和读数据库(Mysql)的查询效率哪家强?

热门文章

  1. 找工作必看:上海知名互联网公司盘点(全)
  2. 应届大硕,上海找工作
  3. 电脑上怎么批量将视频变小
  4. 国家测绘地理信息局重庆测绘院---院科技人才
  5. 微信小程序 getLocation:fail auth deny
  6. leetcode136---异或运算的交换律
  7. C# TCP/IP客户端与服务端数据与文件的传输
  8. Maven 导入 com.sun.javadoc包
  9. Prometheus如何集成免费电话和短信告警
  10. CEM计算电磁学 -- Lecture 2 学习笔记 (1) ---TMM 传输矩阵法(1)