大佬博客
这是一篇质量还行的博客
前排放大佬的博客用来\(orz\)
FlashHu orz
定义
又到了喜闻乐见的百度百科自学时间
真·定义:
动态树,\(\text{Link-Cut\ Tree}\),简称\(\text{LCT}\)
其中的\(\text{Link}\)指加边,\(\text{Cut}\)指删边
类似于树链剖分,但是线段树变成了\(Splay\)(大概是这样的)
我们就可以在树上自由地删边、连边了,当然也可以搞其它的
作用
-
查询、修改链上的信息(最值,总和等)
-
换根
-
动态连边、删边
-
合并两棵树、分离一棵树
-
动态维护连通性
-
敬请期待更多操作
前置芝士
Splay
据说\(\sout\text{Treap}\)也可以,但蒟蒻作者不会
树链剖分(如果你学过的话可以更好理解,没学过也没有关系)
讲解
令人窒息的百度百科(反正我再也不会看了)
如果你一头雾水地回来了,那么我又双叒叕成功了
\(\text{LCT}\)是由很多\(\text{Splay}\)维护的一个数据结构
其中最重要的当然就是\(\sout\text{Splay}\)
零、大概的实现
如果有一棵树,我们将每个点选一个儿子为偏爱儿子(类似于树链剖分的重儿子,但是这是我们自己定的)
然后偏爱儿子与父亲的边为实边,其余的为虚边
每一个实边相连的部分为一棵\(\text{Splay}\)
我们将每一个实边相连的部分(一条链)叫做偏爱路径\((Preferred\ Path)\)
组成的\(\text{Splay}\)叫做辅助树\((Auxiliary\ Tree)\)
学专业术语就是出去装A_C用的
假设我们可以构造这么一棵树,长这样:(红色为实边,绿色为虚边)
其中框出来的部分就是一棵\(\text{Splay}\)
图好丑啊啊啊啊啊!!!
注意不要把\(\text{F}\)给忘了
对于每一棵\(\text{Splay}\),我们按深度的从小到大排序
排序完了之后当然就是:前面(左边)的是祖先,后面(右边)的是后代
\(\text{LCT}\)不画了,\(lazy\),但是\(\text{LCT}\)要满足上面的规则
其中:
虚边认父不认子(如只记录\(F\)的父亲是\(E\),但不记录\(E\)的儿子是\(F\))
实边都要认(两边都要存)
这些东西是为了保证我们接下来的操作
一、\(\text{access}\)
核心操作!!!
如果你英语好,或者知道这个东西
你就可以知道这个单词的意思是访问
具体的,它用来实现把节点\(x\)与树根连接起来(搞到一棵\(\text{Splay}\)里)
跟访问有什么关系呢?
我们举个栗子,把上面的树搬下来:
假设我们要\(\text{access(L)}\),就要把\(A\)到\(L\)路径上的所有边全部变为实边
怎么实现呢?
1.首先我么要把\(L\)转为自己所在的\(\text{Splay}\)的根
注意我画的图不是按照排序规律画的(因为懒)
现在\(K\)在\(L\)的左边
2.然后,由于我们马上就要把\(L\)与\(E\)连成实边了,所以我们要把\(L\)的右儿子赋为空(认父不认子),尽管它并没有右儿子
3.接着,我们更新一下\(L\)的信息(由下到上)
4.最后,我们走向\(L\)的父亲\(E\)(通过虚边走向父亲)
现在我们在\(E\)
把\(E\)转到根
把抢了位置的\(G\)替换为上来的\(L\)(更新右儿子)
更新\(E\)的信息(由下到上)
走向\(E\)的父亲\(A\),注意我们已经换到了根,所以父亲为\(A\)(通过虚边走向父亲)
对\(A\)也进行这样的操作,那么直到再像父亲走的时候走到了空节点,结束
总结,其实循环只有四步:
1.转到根
2.改变右儿子
3.更新信息
4.走向父亲
void access(int x)//访问
{
for(int y = 0; x ;x = t[y = x].f)
{
splay(x);
t[x].ch[1] = y;
up(x);
}
}
二、\(\text{makeroot}\)(换根)
懂了第一个操作,这个操作就显得简单很多
连通\(x\)到根,把\(x\)转到根
其它点的深度都改变了,但是我们发现他们的深度刚好都翻转了过来
所以我们可以这样:
void pushr(int x)//翻转操作
{
swap(t[x].ch[0],t[x].ch[1]);
t[x].r ^= 1;
}
void makeroot(int x)//换根
{
access(x);
splay(x);
pushr(x);
}
三、\(\text{findroot}\)(找根)
就是找当前点所在的\(\text{Splay}\)的根?
Pi!
是原树的根
首先把\(x\)到根连通,把\(x\)转到根,一直向左走,最小的即为根
int findroot(int x)//找根(原树中) ,就是最小的那个
{
access(x);
splay(x);
while(t[x].ch[0]) down(x),x = t[x].ch[0];
splay(x);
return x;
}
四、\(\text{merge}\)(合并)
自己给这个操作取的名字
我们询问\(x,y\)的路径上的信息,就要把\(x,y\)合到一棵树里面
故取名\(merge\),由于\(merge\)是关键字,所以简写为\(merg\) (好丑)
我们将\(x\)置为树的根(\(\text{makeroot}\)),通过\(\text{access}\)操作连通\(x,y\),将\(y\)旋转到根,路径上的点就在\(y\)的子树中,我们可以通过访问\(y\)中的信息,获取路径上的信息
void merg(int x,int y)//提取路径,把x和y搞到一棵Splay里面,再乱搞
{
makeroot(x);
access(y);
splay(y);
}
五、\(\text{link}\)(加边)
判断是否已经连通
如果没连通,连虚边
判连通的两个方法:
1.普遍的
将\(x\)置为根,找\(y\)所在的树的根,如果不是\(x\),不连通,否则连通
2.特殊的:只有加边
并查集维护
void link(int x,int y)//连边
{
makeroot(x);
if(findroot(y) != x) t[x].f = y;
}
六、\(\text{cut}\)(删边)
首先判断是否连通
连通之后再判断是否有一条相连的边
我们可以通过这样的方法判断:
把\(x\)置为根
如果\(y\)的父亲是\(x\)并且\(y\)没有左子树(即\(y\)的祖先就\(x\)一个)
删掉:\(y\)的父亲置为\(0\),\(x\)的右儿子置为\(0\)
别忘了更新\(x\)的信息!!!
void cut(int x,int y)//删边
{
makeroot(x);
if(findroot(y) == x && t[y].f == x && !t[y].ch[0])//保证它们相连
{
t[y].f = t[x].ch[1] = 0;
up(x);
}
}
练习
板题(洛谷)
[COCI 2009] OTOCI / 极地旅行社(洛谷)
弹飞绵羊(洛谷)
洞穴勘测(洛谷)
其实都是板题,就弹飞绵羊稍微有一点思维
代码
板题代码
//12252024832524
#include
#include
using namespace std;
typedef long long LL;
const int MAXN = 100005;
int n,m;
int v[MAXN];
int Read()
{
int x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
void Put1(int x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
void Put(int x)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
}
template T Max(T x,T y){return x > y ? x : y;}
template T Min(T x,T y){return x < y ? x : y;}
template T Abs(T x){return x < 0 ? -x : x;}
struct Splay//LinkCutTree
{
int tot;
int st[MAXN];//栈
struct node
{
int val,ch[2],f;
bool r;//翻转标记
}t[MAXN];
bool nroot(int x)//判断节点是否为一个Splay的根
{
return t[t[x].f].ch[0] == x || t[t[x].f].ch[1] == x;
//如果连的是轻边,他的父亲的儿子里就没有它
}
void pushr(int x)//翻转操作
{
swap(t[x].ch[0],t[x].ch[1]);
t[x].r ^= 1;
}
void up(int x)
{
t[x].val = t[t[x].ch[0]].val ^ t[t[x].ch[1]].val ^ v[x];
}
void down(int x)
{
if(t[x].r)
{
if(t[x].ch[0]) pushr(t[x].ch[0]);
if(t[x].ch[1]) pushr(t[x].ch[1]);
t[x].r = 0;
}
}
bool chk(int x)
{
return (t[t[x].f].ch[1] == x);
}
void rotate(int x)
{
int y = t[x].f;
int z = t[y].f;
int k = chk(x);
int w = t[x].ch[k^1];
t[y].ch[k] = w;
if(w) t[w].f = y;
if(nroot(y)) t[z].ch[chk(y)] = x;
t[x].f = z;
t[x].ch[k^1] = y;
t[y].f = x;
up(y);
up(x);
}
void splay(int x,int to = 0)
{
int y = x,z = 0;
st[++z] = y;
while(nroot(y)) st[++z] = y = t[y].f;
while(z) down(st[z--]);
while(nroot(x))
{
y = t[x].f;
if(nroot(y))
{
if(chk(x) != chk(y)) rotate(x);
else rotate(y);
}
rotate(x);
}
up(x);
}
void access(int x)//访问
{
for(int y = 0; x ;x = t[y = x].f)
{
splay(x);
t[x].ch[1] = y;
up(x);
}
}
void makeroot(int x)//换根
{
access(x);
splay(x);
pushr(x);
}
int findroot(int x)//找根(原树中) ,就是最小的那个
{
access(x);
splay(x);
while(t[x].ch[0]) down(x),x = t[x].ch[0];
splay(x) ;
return x;
}
void merg(int x,int y)//提取路径,把x和y搞到一棵Splay里面,再乱搞
{
makeroot(x);
access(y);
splay(y);
}
void link(int x,int y)//连边
{
makeroot(x);
if(findroot(y) != x) t[x].f = y;
}
void cut(int x,int y)//删边
{
makeroot(x);
if(findroot(y) == x && t[y].f == x && !t[y].ch[0])//保证它们相连
{
t[y].f = t[x].ch[1] = 0;
up(x);
}
}
}lct;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read();
m = Read();
for(int i = 1;i <= n;++ i) v[i] = Read();
while(m --)
{
int opt = Read(),x = Read(),y = Read();
if(opt == 0) {lct.merg(x,y);Put(lct.t[y].val);putchar('\n');}
else if(opt == 1) {lct.link(x,y);}
else if(opt == 2) {lct.cut(x,y);}
else if(opt == 3) {lct.splay(x,0);v[x] = y;}
else printf("fuck ccf\n");
}
return 0;
}
OTOCI代码 (貌似不开O2过不了,令人窒息的常数)
//12252024832524
#include
#include
using namespace std;
typedef long long LL;
const int MAXN = 30005;
int n,m;
int v[MAXN];
int st[MAXN];//栈
int F[MAXN];
char opt[10];
inline int Read()
{
int x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
inline void Put1(int x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
inline void Put(int x)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
}
template T Max(T x,T y){return x > y ? x : y;}
template T Min(T x,T y){return x < y ? x : y;}
template T Abs(T x){return x < 0 ? -x : x;}
char gc()
{
char c = getchar();
while(c > 'z' || c < 'a') c = getchar();
return c;
}
int findSet(int x)
{
if(F[x] != x) F[x] = findSet(F[x]);
return F[x];
}
struct Splay//LinkCutTree
{
int tot;
struct node
{
int val,ch[2],f;
bool r;//翻转标记
}t[MAXN];
inline bool nroot(int x)//判断节点是否为一个Splay的根
{
return t[t[x].f].ch[0] == x || t[t[x].f].ch[1] == x;
//如果连的是轻边,他的父亲的儿子里就没有它
}
inline void pushr(int x)//翻转操作
{
swap(t[x].ch[0],t[x].ch[1]);
t[x].r ^= 1;
}
inline void up(int x)
{
t[x].val = t[t[x].ch[0]].val + t[t[x].ch[1]].val + v[x];
}
inline void down(int x)
{
if(t[x].r)
{
if(t[x].ch[0]) pushr(t[x].ch[0]);
if(t[x].ch[1]) pushr(t[x].ch[1]);
t[x].r = 0;
}
}
inline bool chk(int x)
{
return (t[t[x].f].ch[1] == x);
}
inline void rotate(int x)
{
int y = t[x].f;
int z = t[y].f;
int k = chk(x);
int w = t[x].ch[k^1];
t[y].ch[k] = w;
if(w) t[w].f = y;
if(nroot(y)) t[z].ch[chk(y)] = x;
t[x].f = z;
t[x].ch[k^1] = y;
t[y].f = x;
up(y);
up(x);
}
inline void splay(int x,int to = 0)
{
int y = x,z = 0;
st[++z] = y;
while(nroot(y)) st[++z] = y = t[y].f;
while(z) down(st[z--]);
while(nroot(x))
{
y = t[x].f;
if(nroot(y))
{
if(chk(x) != chk(y)) rotate(x);
else rotate(y);
}
rotate(x);
}
up(x);
}
inline void access(int x)//访问
{
for(int y = 0; x ;x = t[y = x].f)
{
splay(x);
t[x].ch[1] = y;
up(x);
}
}
inline void makeroot(int x)//换根
{
access(x);
splay(x);
pushr(x);
}
inline int findroot(int x)//找根(原树中) ,就是最小的那个
{
access(x);
splay(x);
while(t[x].ch[0]) down(x),x = t[x].ch[0];
splay(x) ;
return x;
}
inline int dis(int x,int y)//询问路径,把x和y搞到一棵Splay里面,再乱搞
{
makeroot(x);
access(y);
splay(y);
return t[y].val;
}
bool qconnect(int x,int y)
{
if(findSet(x) == findSet(y)) return 1;
return 0;
}
inline void link(int x,int y)//连边
{
makeroot(x);
if(!qconnect(x,y)) t[x].f = y,printf("yes\n"),F[findSet(x)] = findSet(y);
else printf("no\n");
}
inline void cut(int x,int y)//删边
{
makeroot(x);
if(qconnect(x,y) && t[y].f == x && !t[y].ch[0])//保证它们相连
{
t[y].f = t[x].ch[1] = 0;
up(x);
}
}
}lct;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read();
for(register int i = 1;i <= n;++ i) v[i] = Read(),F[i] = i;
int x,y;
for(register int Q = Read(); Q ;-- Q)
{
char opt = gc();
x = Read();
y = Read();
if(opt == 'e') {if(!lct.qconnect(x,y)){printf("impossible\n");continue;}Put(lct.dis(x,y));putchar('\n');}
else if(opt == 'b') {lct.link(x,y);}
else if(opt == 'p') {lct.splay(x,0);v[x] = y;}
}
return 0;
}
弹飞绵羊代码
//12252024832524
#include
#include
using namespace std;
typedef long long LL;
const int MAXN = 200005;
int n,m;
int v[MAXN],tox[MAXN];
int st[MAXN];//栈
inline int Read()
{
int x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
inline void Put1(int x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
inline void Put(int x)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
}
template T Max(T x,T y){return x > y ? x : y;}
template T Min(T x,T y){return x < y ? x : y;}
template T Abs(T x){return x < 0 ? -x : x;}
char gc()
{
char c = getchar();
while(c > 'z' || c < 'a') c = getchar();
return c;
}
struct Splay//LinkCutTree
{
int tot;
struct node
{
int val,ch[2],f;
bool r;//翻转标记
}t[MAXN];
inline bool nroot(int x)//判断节点是否为一个Splay的根
{
return t[t[x].f].ch[0] == x || t[t[x].f].ch[1] == x;
//如果连的是轻边,他的父亲的儿子里就没有它
}
inline void pushr(int x)//翻转操作
{
swap(t[x].ch[0],t[x].ch[1]);
t[x].r ^= 1;
}
inline void up(int x)
{
t[x].val = t[t[x].ch[0]].val + t[t[x].ch[1]].val + v[x];
}
inline void down(int x)
{
if(t[x].r)
{
if(t[x].ch[0]) pushr(t[x].ch[0]);
if(t[x].ch[1]) pushr(t[x].ch[1]);
t[x].r = 0;
}
}
inline bool chk(int x)
{
return (t[t[x].f].ch[1] == x);
}
inline void rotate(int x)
{
int y = t[x].f;
int z = t[y].f;
int k = chk(x);
int w = t[x].ch[k^1];
t[y].ch[k] = w;
if(w) t[w].f = y;
if(nroot(y)) t[z].ch[chk(y)] = x;
t[x].f = z;
t[x].ch[k^1] = y;
t[y].f = x;
up(y);
up(x);
}
inline void splay(int x,int to = 0)
{
int y = x,z = 0;
st[++z] = y;
while(nroot(y)) st[++z] = y = t[y].f;
while(z) down(st[z--]);
while(nroot(x))
{
y = t[x].f;
if(nroot(y))
{
if(chk(x) != chk(y)) rotate(x);
else rotate(y);
}
rotate(x);
}
up(x);
}
inline void access(int x)//访问
{
for(int y = 0; x ;x = t[y = x].f)
{
splay(x);
t[x].ch[1] = y;
up(x);
}
}
inline void makeroot(int x)//换根
{
access(x);
splay(x);
pushr(x);
}
inline int findroot(int x)//找根(原树中) ,就是最小的那个
{
access(x);
splay(x);
while(t[x].ch[0]) down(x),x = t[x].ch[0];
splay(x) ;
return x;
}
inline int dis(int x,int y)//询问路径,把x和y搞到一棵Splay里面,再乱搞
{
makeroot(x);
access(y);
splay(y);
return t[y].val;
}
void link(int x,int y)//连边
{
makeroot(x);
if(findroot(y) != x) t[x].f = y;
}
inline void cut(int x,int y)//删边
{
makeroot(x);
if(findroot(y) == x && t[y].f == x && !t[y].ch[0])//保证它们相连
{
t[y].f = t[x].ch[1] = 0;
up(x);
}
}
}lct;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read();
for(register int i = 1;i <= n;++ i)
{
v[i] = 1;
tox[i] = Read();
if(i + tox[i] > n)
tox[i] = n+1-i;
lct.link(i,i+tox[i]);
}
for(register int Q = Read(); Q ;-- Q)
{
int opt = Read();
if(opt == 1) Put(lct.dis(Read()+1,n+1)),putchar('\n');
else if(opt == 2)
{
int x = Read()+1;
lct.cut(x,x+tox[x]);
tox[x] = Read();
if(x + tox[x] > n) tox[x] = n+1-x;
lct.link(x,x+tox[x]);
}
else printf("fuck ccf\n");
}
return 0;
}
洞穴勘测
//12252024832524
#include
#include
using namespace std;
typedef long long LL;
const int MAXN = 10005;
int n,m;
int v[MAXN],tox[MAXN];
int st[MAXN];//栈
inline int Read()
{
int x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
inline void Put1(int x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
inline void Put(int x)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
}
template T Max(T x,T y){return x > y ? x : y;}
template T Min(T x,T y){return x < y ? x : y;}
template T Abs(T x){return x < 0 ? -x : x;}
char gc()
{
char c = getchar();
while(c > 'Z' || c < 'A') c = getchar();
return c;
}
struct Splay//LinkCutTree
{
int tot;
struct node
{
int val,ch[2],f;
bool r;//翻转标记
}t[MAXN];
inline bool nroot(int x)//判断节点是否为一个Splay的根
{
return t[t[x].f].ch[0] == x || t[t[x].f].ch[1] == x;
//如果连的是轻边,他的父亲的儿子里就没有它
}
inline void pushr(int x)//翻转操作
{
swap(t[x].ch[0],t[x].ch[1]);
t[x].r ^= 1;
}
inline void up(int x)
{
t[x].val = t[t[x].ch[0]].val + t[t[x].ch[1]].val + v[x];
}
inline void down(int x)
{
if(t[x].r)
{
if(t[x].ch[0]) pushr(t[x].ch[0]);
if(t[x].ch[1]) pushr(t[x].ch[1]);
t[x].r = 0;
}
}
inline bool chk(int x)
{
return (t[t[x].f].ch[1] == x);
}
inline void rotate(int x)
{
int y = t[x].f;
int z = t[y].f;
int k = chk(x);
int w = t[x].ch[k^1];
t[y].ch[k] = w;
if(w) t[w].f = y;
if(nroot(y)) t[z].ch[chk(y)] = x;
t[x].f = z;
t[x].ch[k^1] = y;
t[y].f = x;
up(y);
up(x);
}
inline void splay(int x,int to = 0)
{
int y = x,z = 0;
st[++z] = y;
while(nroot(y)) st[++z] = y = t[y].f;
while(z) down(st[z--]);
while(nroot(x))
{
y = t[x].f;
if(nroot(y))
{
if(chk(x) != chk(y)) rotate(x);
else rotate(y);
}
rotate(x);
}
up(x);
}
inline void access(int x)//访问
{
for(int y = 0; x ;x = t[y = x].f)
{
splay(x);
t[x].ch[1] = y;
up(x);
}
}
inline void makeroot(int x)//换根
{
access(x);
splay(x);
pushr(x);
}
inline int findroot(int x)//找根(原树中) ,就是最小的那个
{
access(x);
splay(x);
while(t[x].ch[0]) down(x),x = t[x].ch[0];
splay(x) ;
return x;
}
inline int dis(int x,int y)//询问路径,把x和y搞到一棵Splay里面,再乱搞
{
makeroot(x);
access(y);
splay(y);
return t[y].val;
}
bool qconnect(int x,int y)
{
makeroot(x);
if(findroot(y) == x) return 1;
return 0;
}
void link(int x,int y)//连边
{
if(!qconnect(x,y)) t[x].f = y;
}
inline void cut(int x,int y)//删边
{
if(qconnect(x,y) && t[y].f == x && !t[y].ch[0])//保证它们相连
{
t[y].f = t[x].ch[1] = 0;
up(x);
}
}
}lct;
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read();
for(register int Q = Read(); Q ;-- Q)
{
char c = gc();
if(c == 'C') lct.link(Read(),Read());
else if(c == 'D') lct.cut(Read(),Read());
else
{
if(lct.qconnect(Read(),Read())) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}