最近自学了一下LCT(Link-Cut-Tree),参考了Saramanda及Yang_Zhe等众多大神的论文博客,对LCT有了一个初步的认识,LCT是一种动态树,可以处理动态问题的算法。对于树分治中的树链剖分,只能处理静态的数据或者在轻重链上的边或点的权值,对于其他动态的处理就毫无办法了。因此我们就需要引入LCT这个东西。那么问题来了,LCT到底是什么呢?我弄了很久总算是理解了LCT,打算总结一下LCT的基本操作。

①浅谈对LCT的初步印象

LCT用来维护动态的森林,以及一些链上操作,是处理节点无序的有根树组成的森林,进行一些列操作(例如合并,剪切,翻转,更新·····)

对于一棵树的操作,我们可以用splay维护;同理,对于一片森林,也可以用多棵splay维护,而LCT就是要把这些森林的splay联系在一起。而LCT的核心就是access操作啦!

看完之后我们知道,LCT和静态的树链剖分很像。怎么说呢?这两种树形结构都是由若干条长度不等的“重链”和“轻边”构成(名字可以不同,大概就是这个意思),“重链”之间由”轻边”连接。就像这样:

可以想象为一棵树被人为的砍成了一段段。

  LCT和树链剖分不同的是,树链剖分的链是不会变化的,所以可以很方便的用线段树维护。但是,既然是动态树,那么树的结构形态将会发生改变,所以我们要用更加灵活的维护区间的结构来对链进行维护,不难想到Splay可以胜任。如何分离树链也是保证时间效率的关键(链的数量和长度要平衡),树链剖分的“重儿子”就体现了前人博大精深的智慧。

  在这里解释一下为什么要把树砍成一条条的链:我们可以在logn的时间内维护长度为n的区间(链),所以这样可以极大的提高树上操作的时间效率。在树链剖分中,我们把一条条链放到线段树上维护。但是LCT中,由于树的形态变化,所以用能够支持合并、分离、翻转等操作的Splay维护LCT的重链(注意,单独一个节点也算是一条重链)。

  这时我们注意到,LCT中的轻边信息变得无法维护。为什么呢?因为Splay只维护了重链,没有维护重链之间的轻边;而LCT中甚至连根都可以不停的变化,所以也没法用点权表示它父边的边权(父亲在变化)。所以,如果在LCT中要维护边上信息,个人认为最方便的方法应该是把边变成一个新点和两条边。这样可以把边权的信息变成点权维护,同时为了不影响,把真正的树上节点的点权变成0,就可以用维护点的方式维护边。

②再谈LCT的各种操作

  LCT中用Splay维护链,这些Splay叫做“辅助树“。辅助树以它上面每个节点的深度为关键字维护,就是辅助树中每个节点左儿子的深度小于当前节点的深度,当前节点的深度小于右儿子的深度。

  可以把LCT认为是一个由Splay组成的森林,就像这样:(三角形代表一棵Splay,对应着LCT上一条链)

箭头是什么意思呢?箭头记录着某棵Splay对应的链向上由轻边连着哪个节点,可以想象为箭头指向“Splay 的父亲”。但是,Splay的父亲并不记录有这个儿子,即箭头是单向的。同时,每个节点要记录它是否是它所在的Splay的根。这样,Splay构成的森林就建成了。

这个是我的Splay节点最基本的定义:(如果要维护更多信息就像Splay维护区间那样加上更多标记)

 struct node{
int fa,ch[]; //父亲和左右儿子。
bool reverse,is_root; //区间反转标记、是否是所在Splay的根
}T[maxn];

LCT中基本的Splay上操作:

 int getson(int x){
return x==T[T[x].fa].ch[];
}
void pushreverse(int x){
if(!x)return;
swap(T[x].ch[],T[x].ch[]);
T[x].reverse^=;
}
void pushdown(int x){
if(T[x].reverse){
pushreverse(T[x].ch[]);
pushreverse(T[x].ch[]);
T[x].reverse=false;
}
}
void rotate(int x){
if(T[x].is_root)return;
int k=getson(x),fa=T[x].fa;
int fafa=T[fa].fa;
pushdown(fa);pushdown(x); //先要下传标记
T[fa].ch[k]=T[x].ch[k^];
if(T[x].ch[k^])T[T[x].ch[k^]].fa=fa;
T[x].ch[k^]=fa;
T[fa].fa=x;
T[x].fa=fafa;
if(!T[fa].is_root)T[fafa].ch[fa==T[fafa].ch[]]=x;
else T[x].is_root=true,T[fa].is_root=false;
//update(fa);update(x); //如果维护了信息,就要更新节点
}
void push(int x){
if(!T[x].is_root)push(T[x].fa);
pushdown(x);
}
void Splay(int x){
push(x); //在Splay到根之前,必须先传完反转标记
for(int fa;!T[x].is_root;rotate(x)){
if(!T[fa=T[x].fa].is_root){
rotate((getson(x)==getson(fa))?fa:x);
}
}
}

access操作:

这是LCT最核心的操作。其他所有操作都要用到它。

他的含义是”访问某节点“。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。可以理解为专门开辟一条x到根的路径,由一棵Splay维护这条路径。

access之前:(粗的是重链)

access之后:

access实现的方式很简单:先把x旋转到所在Splay的根,然后把x的右孩子的is_root设为true(此时右孩子对应的是x下方的重链,这样就断开了x和下方的重链)。用y记录上一次的x(初始化y=0),把y接到x的右孩子上,这样就把上一次的重链接到了当前重链一起,同时记得T[y].is_root=false。记录y=x,然后x=T[x].fa,把x上提。重复上面的步骤直到x=0。

实现代码如下:

 void access(int x){
int y=;
do{
Splay(x);
T[T[x].ch[]].is_root=true;
T[T[x].ch[]=y].is_root=false;
//update(x); //如果维护了信息记得更新。
x=T[y=x].fa;
}while(x);
}

mroot操作:

  这个操作的作用是把某个节点变成树根(这里的根指的是整棵LCT的根)。加上access操作,就可以方便的提取出LCT上两点之间的路径。提取u到v的路径只需要mroot(u),access(v),然后v所在的Splay对应的链就是u到v的路径。

mroot实现的方式:

  由于LCT是Splay组成的森林,所以要把x变成根就只需要让所有Splay的父亲最终指向x所在Splay。所以先access(x),Splay(x),把现在的根和将成为根的x链在一棵Splay中,并转到根即可。但是我们注意到,由于x成为了新的根,所以它和原来的根所在的Splay中深度作为关键字的性质遭到了破坏:新根x应该是Splay中深度最小的,但是之前的操作并不会改变x的深度(也就是目前x依旧是当前Splay中深度最深的)。所以,我们需要把所在的这棵Splay翻转过来。

(粗的是重链,y是原来的根)

翻转前:

翻转后:

这时候x才真正变成了根。

实现代码如下:

 void mroot(int x){
access(x);
Splay(x);
pushreverse(x);
}

link操作:

这个操作的作用是连接两棵LCT。对于link(u,v),表示连接u所在的LCT和v所在的LCT;

link实现的方式:

很简单,只需要先mroot(u),然后记录T[u].fa=v就可以了,就是把一个Splay森林连到另一个上。

实现代码如下:

void link(int u,int v){
mroot(u);
T[u].fa=v;
}

cut操作:

  这个操作的作用是分离出两棵LCT。

实现代码如下:

 void cut(int u,int v)
mroot(u); //先把u变成根
access(v);Splay(v); //连接u、v
pushdown(v); //先下传标记
T[u].fa=T[v].ch[]=;
//v的左孩子表示v上方相连的重链
//update(v); //记得维护信息
}

以上这些就是LCT的基本操作啦。

③例题分析

先来一道简单的入门题

Time Limit: 851MS   Memory Limit: 1572864KB   64bit IO Format: %lld & %llu

Submit Status

Description

You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3...N-1.

We will ask you to perfrom some instructions of the following form:

  • CHANGE i ti : change the cost of the i-th edge to ti
    or
  • QUERY a b : ask for the maximum edge cost on the path from node a to node b

Input

The first line of input contains an integer t, the number of test cases (t <= 20). t test cases follow.

For each test case:

  • In the first line there is an integer N (N <= 10000),
  • In the next N-1 lines, the i-th line describes the i-th edge: a line with three integers a b c denotes an edge between ab of cost c (c <= 1000000),
  • The next lines contain instructions "CHANGE i ti" or "QUERY a b",
  • The end of each test case is signified by the string "DONE".

There is one blank line between successive tests.

Output

For each "QUERY" operation, write one integer representing its result.

Example

Input:
1 3
1 2 1
2 3 2
QUERY 1 2
CHANGE 1 3
QUERY 1 2
DONE Output:
1
3

Hint

Thanh-Vy Hua Date: 2005-06-08 Time limit: 0.851s Source limit: 15000B Memory limit: 1536MB Cluster: Cube (Intel G860) Languages: ADA ASM BASH BF C C# C++ 5 CLPS LISP sbcl LISP clisp D FORT HASK ICON ICK JAVA LUA NEM NICE CAML PAS gpc PAS fpc PERL PHP PIKE PRLG PYTH 2.7 RUBY SCM qobi SCM guile ST TEXT WSPC

Submit Status

咦,这道题不是树链剖分的入门题么?  对的,不过,也可以用LCT做(树链剖分只处理静态的树和有关轻重边的动态更新,而LCT就是用来解决动态树问题)

题意:

给定一棵树,给定每条边a,b,w  (w权值)

完成两个操作:

1.把第i条边权值改成w

2.查询u到v路径上的最大权值

介绍一下access操作:查询根到u这条路径,并把这条路径更新为偏爱路径(之前的偏爱边可能会有改变)被访问到的u,不再有偏爱儿子。

每次access访问,都会把这条路径上的点用一棵splay维护,splay维护的关键词是点的深度,保证左边的子树比当前点深度小,右边的子树比当前点深度大。

并且这棵树把u作为根节点之后不再有右儿子(u没有偏爱儿子).

而splay要维护的是一条偏爱路径上的点。所有的splay会构成一个森林的集合。

要注意的是,在rotate判断ff的时候,有可能ff的左右儿子都不是f,然而没有进行判断就会跑出神奇的错误。

实现代码如下:

 #include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<algorithm>
using namespace std;
/*
LCT的思想类似于轻重链剖分
是用偏爱路径划分,用splay维护一条偏爱路径,然后像森林操作一样搞
u-v边的权值记录在v点上
*/
#define maxn 10005
int n;
int fa[maxn];
struct edge
{
int v,w;
edge *next;
edge(int _v,int _w,edge *_next)
{
v=_v;
w=_w;
next=_next;
}
}*head[maxn];
struct node
{
node *f;
node *ch[];
bool root;//是否是所在辅助树的根节点
int cost;
int maxcost; }tree[maxn],*null,Tnull;
void init(node *u)
{
u->f=u->ch[]=u->ch[]=null;
u->root=true;
u->cost=u->maxcost=;
}
void pushup(node *p)
{
p->maxcost=max(max(p->ch[]->maxcost,p->ch[]->maxcost),p->cost);
}
void rotate(node *u)
{
node *f=u->f;
node *ff=f->f;
int d=u==f->ch[]; f->ch[d]=u->ch[d^];
if(u->ch[d^]!=null)u->ch[d^]->f=f; u->f=ff;
if(ff!=null)
{
if(ff->ch[] == f)
{
ff->ch[] = u;
}
else if(ff->ch[] == f)//一定要用Else If,如果直接用Else会出现错误,因为树本身可能不是二叉树,虽然生成的Splay Tree是
{
ff->ch[] = u;
}
} u->ch[d^]=f;
f->f=u; pushup(f);
pushup(u);
if(f->root)
{
u->root=true;
f->root=false;
}
}
void splay(node *u)
{
if(u==null)return ;
while(!u->root)
{
node *f=u->f;
if(f->root)
{
rotate(u);
}
else
{
node *ff=f->f;
int d=u==f->ch[];
int dd=f==ff->ch[];
if(d==dd)rotate(f);
else rotate(u);
rotate(u);
}
}
pushup(u);
}
vector<pair<int,int> >E;
char ss[];
void access(node *u,bool flag)
{
node *v=null;
while(u!=null)
{
splay(u);
if(flag)
{
if(u->f==null)
{
printf("%d\n",max(u->ch[]->maxcost,v->maxcost));
}
}
u->ch[]->root=true;
u->ch[]=v;
v->root=false;
pushup(u);
v=u;
u=u->f;
}
}
void query(int u,int v)
{
access(tree+u,);
access(tree+v,);
}
void change(int u,int w)
{
access(tree+u,);
splay(tree+u);
tree[u].cost=w;
pushup(tree+u);
}
void bfs()
{
memset(fa,-,sizeof(fa));
queue<int >q;
q.push();
fa[]=;
while(!q.empty())
{
int u=q.front();
q.pop();
for(edge *i=head[u];i;i=i->next)
{
if(fa[i->v]==-)
{
fa[i->v]=u;
tree[i->v].f=tree+u;
tree[i->v].cost=tree[i->v].maxcost=i->w;
q.push(i->v);
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
null=&Tnull;
init(null);
while(T--)
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
head[i]=NULL;
init(&tree[i]);
}
E.clear();
for(int i=;i<n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
head[a]=new edge(b,c,head[a]);
head[b]=new edge(a,c,head[b]);
E.push_back(make_pair(a,b));
}
bfs();
while(scanf("%s",ss)!=EOF)
{
if(ss[]=='D')break;
if(ss[]=='Q')
{
int u,v;
scanf("%d%d",&u,&v);
query(u,v);
}
else if(ss[]=='C')
{
int a,w;
scanf("%d%d",&a,&w);
a--;
int u=E[a].first;
int v=E[a].second;
if(fa[u]==v)
{
change(u,w);
}
if(fa[v]==u)
{
change(v,w);
}
}
}
}
return ;
}

一道入门题怎么够呢?那就再来一道操作多一点的~~~

Query on The Trees

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others)
Total Submission(s): 6250    Accepted Submission(s): 2504

Problem Description
We have met so many problems on the tree, so today we will have a query problem on a set of trees.
There are N nodes, each node will have a unique weight Wi. We will have four kinds of operations on it and you should solve them efficiently. Wish you have fun!

 
Input
There are multiple test cases in our dataset.
For each case, the first line contains only one integer N.(1 ≤ N ≤ 300000) The next N‐1 lines each contains two integers x, y which means there is an edge between them. It also means we will give you one tree initially.
The next line will contains N integers which means the weight Wi of each node. (0 ≤ Wi ≤ 3000)
The next line will contains an integer Q. (1 ≤ Q ≤ 300000) The next Q lines will start with an integer 1, 2, 3 or 4 means the kind of this operation.
1. Given two integer x, y, you should make a new edge between these two node x and y. So after this operation, two trees will be connected to a new one.
2. Given two integer x, y, you should find the tree in the tree set who contain node x, and you should make the node x be the root of this tree, and then you should cut the edge between node y and its parent. So after this operation, a tree will be separate into two parts.
3. Given three integer w, x, y, for the x, y and all nodes between the path from x to y, you should increase their weight by w.
4. Given two integer x, y, you should check the node weights on the path between x and y, and you should output the maximum weight on it.
 
Output
For each query you should output the correct answer of it. If you find this query is an illegal operation, you should output ‐1.
You should output a blank line after each test case.
 
Sample Input

5

1 2

2 4

2 5

1 3

1 2 3 4 5

6

4 2 3

2 1 2

4 2 3

1 3 5

3 2 1 4

4 1 4

Sample Output
3
-1
7

Hint

We define the illegal situation of different operations:
In first operation: if node x and y belong to a same tree, we think it's illegal.
In second operation: if x = y or x and y not belong to a same tree, we think it's illegal.
In third operation: if x and y not belong to a same tree, we think it's illegal.
In fourth operation: if x and y not belong to a same tree, we think it's illegal.

Source

题意:

给定一棵树,维护4个操作:

1.给定u,v,如果不在一棵树上,它们之间连边。

2.给定u,v如果u!=v且在同一棵树上,把u设为整棵树的根,再把v和它的父亲断开。

3.给定u,v,w,如果u,v在同一条树上,把他们之间的点的权值+w

4.给定u,v,如果u,v在同一棵树上,求出他们路径上的最大值。

违法操作输出-1

思路:

连边操作,访问u,把u旋到根,把它左右翻转即可(翻转前splay上u一定没有右节点(没有偏爱儿子),翻转后一定没有左儿子,说明它就是从根到u路径上深度最小的了,就成为了根),把它接到v上,设为整棵树的根:一样的先access(u),splay(u)再rev(u)。

断开:access(u),splay(u),但不翻转,这样u没有右儿子但有左儿子,和左子树断开即可。

对于3、4操作,可以先把u设为根,再access(v)然后找到u-v这条路径上splay的根节点在哪里,信息都记录在那个节点上!

实现代码如下:

 #include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
const int maxn=+;
const int inf=0x3f3f3f3f;
struct node
{
node *f;
node *ch[];
bool rev;
int add;
int mm;
int key;
}tree[maxn],*null,*cur;
void init()
{
null=tree;
null->f=null->ch[]=null->ch[]=null;
null->rev=;
null->add=;
null->mm=null->key=-inf;
cur=tree+;
}
node *newnode(int key)
{
cur->f=cur->ch[]=cur->ch[]=null;
cur->mm=cur->key=key;
cur->add=;
cur->rev=;
return cur++;
}
bool isroot(node *x)
{
return x==null||x->f->ch[]!=x&&x->f->ch[]!=x;
}
void pushup(node *u)
{
u->mm=max(u->key,max(u->ch[]->mm,u->ch[]->mm));
}
void pushdown(node *u)
{
if(u==null)return ;
if(u->rev)
{
swap(u->ch[],u->ch[]);
if(u->ch[]!=null)u->ch[]->rev^=;
if(u->ch[]!=null)u->ch[]->rev^=;
u->rev=;
}
if(u->add)
{
if(u->ch[]!=null)
{
u->ch[]->add+=u->add;
u->ch[]->mm+=u->add;
u->ch[]->key+=u->add;
}
if(u->ch[]!=null)
{
u->ch[]->add+=u->add;
u->ch[]->mm+=u->add;
u->ch[]->key+=u->add;
}
u->add=;
}
}
void rotate(node *u)
{
node *f=u->f;
node *ff=f->f;
int d=u==f->ch[]; if(u->ch[d^]!=null)u->ch[d^]->f=f;
f->ch[d]=u->ch[d^]; u->f=ff;
if(ff!=null)
{
if(f==ff->ch[])ff->ch[]=u;
else if(f==ff->ch[])ff->ch[]=u;
} u->ch[d^]=f;
f->f=u; pushup(f);
pushup(u);
}
node *sta[maxn];
int cnt;
void splay(node *u)
{
if(u==null)return ;
cnt=;
sta[]=u;
for(node *y=u;!isroot(y);y=y->f)
{
sta[cnt++]=y->f;
}
while(cnt)pushdown(sta[--cnt]);
while(!isroot(u))
{
node *f=u->f;
node *ff=f->f;
if(isroot(f))
{
rotate(u);
}
else
{
int d=u==f->ch[];
int dd=f==ff->ch[];
if(d==dd)rotate(f);
else rotate(u);
rotate(u);
}
}
pushup(u);
}
node *access(node *u)
{
node *v=null;
while(u!=null)
{
splay(u);
v->f=u;
u->ch[]=v;
pushup(u);
v=u;
u=u->f;
}
return v;
}
void link(node *u,node *v)
{
access(u);
splay(u);
u->rev=;
u->f=v;
}
bool sam(node *u,node *v)
{
while(u->f!=null)u=u->f;
while(v->f!=null)v=v->f;
return u==v;
}
void changeroot(node *u)
{
access(u)->rev^=;
}
void cut(node *u)
{
access(u);
splay(u);//access+旋转之后左子树的点都比u小,一定会有u的父亲
u->ch[]=u->ch[]->f=null;
pushup(u);
}
node *getroot(node *u)
{
access(u);
splay(u);
while(u->f!=null)u=u->f;
splay(u);
return u;
}
int n,m;
int det[maxn];
struct edge
{
int u,v;
}E[maxn];
int main()
{
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=;i<n;i++)
{
scanf("%d%d",&E[i].u,&E[i].v);
}
for(int i=;i<=n;i++)
{
scanf("%d",&det[i]);
newnode(det[i]);
}
for(int i=;i<n;i++)
{
link(tree+E[i].u,tree+E[i].v);
}
scanf("%d",&m);
for(int i=;i<=m;i++)
{
int k,a,b;
scanf("%d",&k);
if(k==)
{
scanf("%d%d",&a,&b);
if(sam(tree+a,tree+b))printf("-1\n");
else
{
link(tree+a,tree+b);
}
}
else if(k==)
{
scanf("%d%d",&a,&b);
if(a==b||!sam(tree+a,tree+b))printf("-1\n");
else
{
changeroot(tree+a);
cut(tree+b);
}
}
else if(k==)
{
int w;
scanf("%d%d%d",&w,&a,&b);
if(!sam(tree+a,tree+b))printf("-1\n");
else
{
changeroot(tree+a);
access(tree+b);
node *q=getroot(tree+b);
q->add+=w;
q->mm+=w;
q->key+=w;
}
}
else if(k==)
{
scanf("%d%d",&a,&b);
if(!sam(tree+a,tree+b))printf("-1\n");
else
{
changeroot(tree+a);
access(tree+b);
node *q=getroot(tree+b);
printf("%d\n",q->mm);
}
}
}
printf("\n");
}
return ;
}

④习题推荐

我推荐几个LCT的练习题练习下上述的知识点吧!

  • BZOJ 2049 SDOI2008洞穴勘探,模板题,只需要link和cut,然后询问连通性。
  • BZOJ 2002 HNOI2010弹飞绵羊,模板题,需要link和询问某点到根的路径长度。

  • BZOJ 3669 NOI2014魔法森林,LCT的综合应用。

相信做完上述例题,你也能对LCT有了个初步的认识吧,加油!

⑤参考资料

LCT学习笔记的更多相关文章

  1. LCT 学习笔记

    LCT学习笔记 前言 自己定的学习计划看起来完不成了(两天没学东西,全在补题),决定赶快学点东西 于是就学LCT了 简介 Link/Cut Tree是一种数据结构,我们用它解决动态树问题 但是LCT不 ...

  2. [总结] LCT学习笔记

    \(emmm\)学\(lct\)有几天了,大概整理一下这东西的题单吧 (部分参考flashhu的博客) 基础操作 [洛谷P1501Tree II] 题意 给定一棵树,要求支持 链加,删边加边,链乘,询 ...

  3. SPLAY,LCT学习笔记(六)

    这应该暂时是个终结篇了... 最后在这里讨论LCT的一个常用操作:维护虚子树信息 这也是一个常用操作 下面我们看一下如何来维护 以下内容转自https://blog.csdn.net/neither_ ...

  4. SPLAY,LCT学习笔记(五)

    这一篇重点探讨LCT的应用 例:bzoj 2631 tree2(国家集训队) LCT模板操作之一,利用SPLAY可以进行区间操作这一性质对维护懒惰标记,注意标记下传顺序和如何下传 #include & ...

  5. SPLAY,LCT学习笔记(四)

    前三篇好像变成了SPLAY专题... 这一篇正式开始LCT! 其实LCT就是基于SPLAY的伸展操作维护树(森林)连通性的一个数据结构 核心操作有很多,我们以一道题为例: 例:bzoj 2049 洞穴 ...

  6. SPLAY,LCT学习笔记(一)

    写了两周数据结构,感觉要死掉了,赶紧总结一下,要不都没学明白. SPLAY专题: 例:NOI2005 维修数列 典型的SPLAY问题,而且综合了SPLAY常见的所有操作,特别适合新手入门学习(比如我这 ...

  7. 【动态树问题】LCT学习笔记

    我居然还不会LCT QAQ真是太弱了 必须学LCT QAQ ------------------线割分是我www------------ LinkCut-Tree是基于Splay(由于Splay能够非 ...

  8. SPLAY,LCT学习笔记(三)

    前两篇讲述了SPLAY模板操作,这一篇稍微介绍一下SPLAY的实际应用 (其实只有一道题,因为本蒟蒻就写了这一个) 例:bzoj 1014火星人prefix 由于本蒟蒻不会后缀数组,所以题目中给的提示 ...

  9. SPLAY,LCT学习笔记(二)

    能够看到,上一篇的代码中有一段叫做find我没有提到,感觉起来也没有什么用,那么他的存在意义是什么呢? 接下来我们来填一下这个坑 回到我们的主题:NOI 2005维修数列 我们刚刚讨论了区间翻转的操作 ...

随机推荐

  1. oracle得到日期对应的星期

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp56   select to_char(sysdate,'ww') fro ...

  2. iOS block和代理的区别

      block和代理是iOS开发中实现回调的两种方式,大多数情况下是用哪个都可以,主要看个人喜好.本文主要是对两者做一下对比. 1.block简介   在 iOS中, block一共分三种.   (1 ...

  3. ABB-robotstudio离线编程-Smart组件资料-笔记

    Smart组件 目录: 1.“信号与属性”子组件. 2.“参数与建模”子组件. 3.“传感器”子组件. 4.“动作”子组件. 5.“本体”子组件. 6.“其他”子组件. 一.“信号与属牲”子组件 1. ...

  4. 如何在sublime+chrome中调试php代码?

    1.搭建php本地运行环境具体点击如何使用phpstudy本地搭建多站点(每个站点对应不同的端口) 2.下载php_xdebug.dll, [5.3版以上的php下载地址]http://pecl.ph ...

  5. 【深入Java虚拟机】之八:Java垃圾收集机制

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/18076173 对象引用 Java中的垃圾回收一般是在Java堆中进行,因为堆中几乎存放了J ...

  6. java 反射详解

    反射的概念和原理 类字节码文件是在硬盘上存储的,是一个个的.class文件.我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个 ...

  7. 201521123106 《Java程序设计》第5周学习总结

    1. 本章学习总结 2. 书面作业 Q1. 代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出结果. 答 ...

  8. 201521123049 《JAVA程序设计》 第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  9. 多线程面试题系列(5):经典线程同步 关键段CS

    上一篇提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题.本文首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现机制与原理.关键段CRITIC ...

  10. javascript:12种JavaScript MVC框架之比较

    Gordon L. Hempton是西雅图的一位黑客和设计师,他花费了几个月的时间研究和比较了12种流行的JavaScript MVC框架,并在博客中总结了每种框架的优缺点,最终的结果是,Ember. ...