模板参考:https://blog.csdn.net/saramanda/article/details/55253627

综合各位大大博客后整理的模板:

 #include<iostream>
#include<cstdio>
using namespace std;
const int maxn = + ;
struct LCT
{
struct node
{
int fa, ch[]; //父亲(Splay对应的链向上由轻边连着哪个节点)、左右儿子
int reverse;//区间反转标记
bool is_root; //是否是所在Splay的根
int siz;
}Tree[maxn];
int n; void init(int MN)
{
for (int i = ; i <= MN; i++)
{
Tree[i].reverse = Tree[i].fa = Tree[i].ch[] = Tree[i].ch[] = ;
Tree[i].is_root = true;
Tree[i].siz = ; }
} bool getson(int x)
{//x是否为重儿子
return x == Tree[Tree[x].fa].ch[];
}
bool isroot(int x)
{
return Tree[Tree[x].fa].ch[] != x && Tree[Tree[x].fa].ch[] != x;
}
void pushreverse(int x)
{
if (!x)return;
swap(Tree[x].ch[], Tree[x].ch[]);
Tree[x].reverse ^= ;
}
void pushdown(int x)
{//下传反转标记
if (Tree[x].reverse)
{
pushreverse(Tree[x].ch[]);
pushreverse(Tree[x].ch[]);
Tree[x].reverse = ;
}
} void update(int x)
{
int l = Tree[x].ch[], r = Tree[x].ch[];
Tree[x].siz = ;
if (l) Tree[x].siz += Tree[l].siz;
if (r) Tree[x].siz += Tree[r].siz;
} void rotate(int x)
{//将x旋转为根
if (Tree[x].is_root)return;
int k = getson(x), fa = Tree[x].fa;
int fafa = Tree[fa].fa;
pushdown(fa); pushdown(x); //先要下传标记
Tree[fa].ch[k] = Tree[x].ch[k ^ ];
if (Tree[x].ch[k ^ ])Tree[Tree[x].ch[k ^ ]].fa = fa;
Tree[x].ch[k ^ ] = fa;
Tree[fa].fa = x;
Tree[x].fa = fafa;
if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[]] = x;
else Tree[x].is_root = true, Tree[fa].is_root = false;
update(fa);update(x); //如果维护了信息,就要更新节点
}
void push(int x)
{
if (!Tree[x].is_root) push(Tree[x].fa);
pushdown(x);
}
int findroot(int x)
{//找到x在原树中的根节点
access(x); Splay(x);
pushdown(x);
while (Tree[x].ch[]) pushdown(x = Tree[x].ch[]);//找到深度最小的点即为根节点
return x;
}
void Splay(int x)
{//让x成为Splay的根,且x不含右儿子
push(x); //在Splay到根之前,必须先传完反转标记
for (int fa; !Tree[x].is_root; rotate(x)) {
if (!Tree[fa = Tree[x].fa].is_root) {
rotate((getson(x) == getson(fa)) ? fa : x);
}
}
}
void access(int x)
{//访问某节点。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。结束后x没有右儿子(没有深度比他大的点)
int y = ;
do {
Splay(x);
Tree[Tree[x].ch[]].is_root = true;
Tree[Tree[x].ch[] = y].is_root = false;
update(x); //如果维护了信息记得更新。
x = Tree[y = x].fa;
} while (x);
}
void mroot(int x)
{//把某个节点变成树根(这里的根指的是整棵LCT的根)
access(x);//使x与根结点处在同一棵splay中
Splay(x);//x成为这棵splay的根,x只有左儿子
//由于根节点所在的splay中,根节点没有左儿子(没有深度比他小的节点),将x的左右子树翻转
pushreverse(x);
}
void link(int u, int v)
{//连接u所在的LCT和v所在的LCT
mroot(u);//先让u成为其所在LCT的根
if(findroot(v)!=u)Tree[u].fa = v;//如果u与v不在同一棵splay中,就把v设置为u的父亲
}
void cut(int u, int v)
{//分离出两棵LCT
mroot(u); //先让u成为其所在LCT的根
access(v); //让u和v在同一棵Splay中
Splay(v); //连接u、v,u是v的左儿子
pushdown(v); //先下传标记
if (Tree[v].ch[])
{
Tree[Tree[v].ch[]].fa = Tree[v].fa;
Tree[Tree[v].ch[]].is_root = true;
}
Tree[v].fa = ; Tree[v].ch[] = ;
//v的左孩子表示v上方相连的重链
update(v); //记得维护信息
} bool judge(int u, int v)
{//判断u和v是否连通
while (Tree[u].fa) u = Tree[u].fa;
while (Tree[v].fa) v = Tree[v].fa;
return u == v;
}
void split(int u, int v)
{//获取u->v的路径
mroot(u);//让u成为根结点
access(v);//访问v
Splay(v);//把v转到根结点,此时u的父亲为v
}
int Query_deep(int x)
{//询问x到LCT根的距离(深度)
access(x);
Splay(x);
return Tree[x].siz;
} void modify(int x,int v)
{//改变点值
access(x);
Splay(x);
//Tree[x].val = v;更改值
update(x); } }lct;
int main()
{ return ;
}

几个知识点:

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

2、LCT相当于多棵splay被虚线连在一起,即splay森林;而最开始的时候是N个单独的点与他的父亲用虚线相连,每个点是一棵splay。

3、无论树怎样旋转,怎样变换,读入时所连接(link)的边(没有被cut的),一直都连着的

4、在每棵splay中每一个结点左子树中的节点都是他在原树中的祖先,右子树中的结点都是他在原树中的孩子。

5、splay森林实例:

原树:

一种可能的splay森林:

6、access(x)操作:

7、splay(x)操作:

—————————题目—————————

1、Cave 洞穴勘测 HYSBZ - 2049

  题意:一开始有n个洞穴,两两之间没有通道。每次将两个洞穴连接或者两个洞穴之间的通道摧毁,或者询问两个洞穴之间能否连通。

  思路:LCT模板题。连接则通过link(u,v)实现,摧毁通过cut(u,v)实现,两个洞穴能否连通则考虑u的根和v的根是否相同。

 #include<iostream>
#include<cstdio>
using namespace std;
const int maxn = + ;
struct LCT
{
struct node
{
int fa, ch[]; //父亲(Splay对应的链向上由轻边连着哪个节点)、左右儿子
int reverse;//区间反转标记
bool is_root; //是否是所在Splay的根
//int siz;
//int val;
//int sum;
}Tree[maxn];
int n;
//int v[maxn];//每个结点的值
void init()
{
for (int i = ; i <= n; i++)
{
Tree[i].reverse = Tree[i].fa = Tree[i].ch[] = Tree[i].ch[] = ;
Tree[i].is_root = true;
//Tree[i].siz = 1;
//Tree[i].val = Tree[i].sum = v[i];
}
} bool getson(int x)
{//x是否为重儿子
return x == Tree[Tree[x].fa].ch[];
}
bool isroot(int x)
{
return Tree[Tree[x].fa].ch[] != x && Tree[Tree[x].fa].ch[] != x;
}
void pushreverse(int x)
{
if (!x)return;
swap(Tree[x].ch[], Tree[x].ch[]);
Tree[x].reverse ^= ;
}
void pushdown(int x)
{//下传反转标记
if (Tree[x].reverse)
{
pushreverse(Tree[x].ch[]);
pushreverse(Tree[x].ch[]);
Tree[x].reverse = ;
}
}
/*
void update(int x)
{
int l = Tree[x].ch[0], r = Tree[x].ch[1];
Tree[x].siz = Tree[l].siz + Tree[r].siz + 1;
Tree[x].sum = Tree[l].sum + Tree[r].sum + Tree[x].val;
}
*/
void rotate(int x)
{//将x旋转为根
if (Tree[x].is_root)return;
int k = getson(x), fa = Tree[x].fa;
int fafa = Tree[fa].fa;
pushdown(fa); pushdown(x); //先要下传标记
Tree[fa].ch[k] = Tree[x].ch[k ^ ];
if (Tree[x].ch[k ^ ])Tree[Tree[x].ch[k ^ ]].fa = fa;
Tree[x].ch[k ^ ] = fa;
Tree[fa].fa = x;
Tree[x].fa = fafa;
if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[]] = x;
else Tree[x].is_root = true, Tree[fa].is_root = false;
//update(fa);update(x); //如果维护了信息,就要更新节点
}
void push(int x)
{
if (!Tree[x].is_root) push(Tree[x].fa);
pushdown(x);
}
int getFa(int x)
{//寻找x在原树的父亲
access(x);
Splay(x);
while (Tree[x].ch[]) x = Tree[x].ch[];
return x;
}
void Splay(int x)
{//让x成为Splay的根
push(x); //在Splay到根之前,必须先传完反转标记
for (int fa; !Tree[x].is_root; rotate(x)) {
if (!Tree[fa = Tree[x].fa].is_root) {
rotate((getson(x) == getson(fa)) ? fa : x);
}
}
}
void access(int x)
{//访问某节点。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。
int y = ;
do {
Splay(x);
Tree[Tree[x].ch[]].is_root = true;
Tree[Tree[x].ch[] = y].is_root = false;
//update(x); //如果维护了信息记得更新。
x = Tree[y = x].fa;
} while (x);
}
void mroot(int x)
{//把某个节点变成树根(这里的根指的是整棵LCT的根)
access(x);//使x与根结点处在同一棵splay中
Splay(x);//x成为这棵splay的根,x只有左儿子
pushreverse(x);
}
void link(int u, int v)
{//连接u所在的LCT和v所在的LCT
mroot(u);//先让u成为其所在LCT的根
Tree[u].fa = v;
}
void cut(int u, int v)
{//分离出两棵LCT
mroot(u); //先让u成为其所在LCT的根
access(v); //让u和v在同一棵Splay中
Splay(v); //连接u、v,u是v的左儿子
pushdown(v); //先下传标记
Tree[Tree[v].ch[]].fa = Tree[v].fa;
Tree[Tree[v].ch[]].is_root = true;
Tree[v].fa = ;
Tree[v].ch[] = ;
//v的左孩子表示v上方相连的重链
//update(v); //记得维护信息
}
bool judge(int u, int v)
{//判断u和v是否连通
while (Tree[u].fa) u = Tree[u].fa;
while (Tree[v].fa) v = Tree[v].fa;
return u == v;
}
}lct;
int main()
{
int m;
while (~scanf("%d%d", &lct.n, &m))
{
lct.init();
char op[];
int u, v;
for (int i = ; i <= m; i++)
{
scanf("%s%d%d", op, &u, &v);
if (op[] == 'Q')
{
if (lct.judge(u, v)) printf("Yes\n");
else printf("No\n");
}
else if (op[] == 'C')
{
lct.link(u, v);
}
else
{
lct.cut(u, v);
}
}
}
return ;
}

2、Bounce 弹飞绵羊 HYSBZ - 2002

  题意:有n个弹射装置,当绵羊在第i个弹射装置时,会被弹射到第i+val[i]个弹射装置,val数组记录每个弹射装置的弹射系数。有两个操作,要么询问当绵羊站在第x个弹射装置时会经过多少次被弹飞,要么修改某个弹射装置的系数。

  思路:可以将第i个弹射装置与第i+val[i]个装置相连,新增第n+1个点作为树根,表示被弹飞。询问次数时即询问当前x到树根的距离即深度,然后-1即可(第n+1个结点不弹射);修改某个弹射装置的系数相当于修改某个结点的父亲,先将原父亲删除,再重新连接父亲。

 #include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = + ;
int val[maxn];
struct LCT
{
struct node
{
int fa, ch[]; //父亲(Splay对应的链向上由轻边连着哪个节点)、左右儿子
int reverse;//区间反转标记
bool is_root; //是否是所在Splay的根
int siz;
}Tree[maxn];
int n;
void init(int maxn)
{
for (int i = ; i <= maxn; i++)
{
Tree[i].reverse = Tree[i].fa = Tree[i].ch[] = Tree[i].ch[] = ;
Tree[i].is_root = true;
Tree[i].siz = ;
}
} bool getson(int x)
{//x是否为重儿子
return x == Tree[Tree[x].fa].ch[];
}
bool isroot(int x)
{
return Tree[Tree[x].fa].ch[] != x && Tree[Tree[x].fa].ch[] != x;
}
void pushreverse(int x)
{
if (!x)return;
swap(Tree[x].ch[], Tree[x].ch[]);
Tree[x].reverse ^= ;
}
void pushdown(int x)
{//下传反转标记
if (Tree[x].reverse)
{
pushreverse(Tree[x].ch[]);
pushreverse(Tree[x].ch[]);
Tree[x].reverse = ;
}
} void update(int x)
{
int l = Tree[x].ch[], r = Tree[x].ch[];
Tree[x].siz = ;
if (l) Tree[x].siz += Tree[l].siz;
if (r) Tree[x].siz += Tree[r].siz;
} void rotate(int x)
{//将x旋转为根
if (Tree[x].is_root)return;
int k = getson(x), fa = Tree[x].fa;
int fafa = Tree[fa].fa;
pushdown(fa); pushdown(x); //先要下传标记
Tree[fa].ch[k] = Tree[x].ch[k ^ ];
if (Tree[x].ch[k ^ ])Tree[Tree[x].ch[k ^ ]].fa = fa;
Tree[x].ch[k ^ ] = fa;
Tree[fa].fa = x;
Tree[x].fa = fafa;
if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[]] = x;
else Tree[x].is_root = true, Tree[fa].is_root = false;
update(fa);update(x); //如果维护了信息,就要更新节点
}
void push(int x)
{
if (!Tree[x].is_root) push(Tree[x].fa);
pushdown(x);
}
int getFa(int x)
{//寻找x在原树的父亲
access(x);
Splay(x);
while (Tree[x].ch[]) x = Tree[x].ch[];
return x;
}
void Splay(int x)
{//让x成为Splay的根
push(x); //在Splay到根之前,必须先传完反转标记
for (int fa; !Tree[x].is_root; rotate(x)) {
if (!Tree[fa = Tree[x].fa].is_root) {
rotate((getson(x) == getson(fa)) ? fa : x);
}
}
}
void access(int x)
{//访问某节点。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。
int y = ;
do {
Splay(x);
Tree[Tree[x].ch[]].is_root = true;
Tree[Tree[x].ch[] = y].is_root = false;
update(x); //如果维护了信息记得更新。
x = Tree[y = x].fa;
} while (x);
}
void mroot(int x)
{//把某个节点变成树根(这里的根指的是整棵LCT的根)
access(x);//使x与根结点处在同一棵splay中
Splay(x);//x成为这棵splay的根,x只有左儿子
pushreverse(x);
}
void link(int u, int v)
{//连接u所在的LCT和v所在的LCT
mroot(u);//先让u成为其所在LCT的根
Tree[u].fa = v;
Tree[u].is_root = true;
}
void cut(int u, int v)
{//分离出两棵LCT
mroot(u); //先让u成为其所在LCT的根
access(v); //让u和v在同一棵Splay中
Splay(v); //连接u、v,u是v的左儿子
pushdown(v); //先下传标记
Tree[Tree[v].ch[]].fa = Tree[v].fa;
Tree[Tree[v].ch[]].is_root = true;
Tree[v].fa = ;
Tree[v].ch[] = ;
//v的左孩子表示v上方相连的重链
update(v); //记得维护信息
}
bool judge(int u, int v)
{//判断u和v是否连通
while (Tree[u].fa) u = Tree[u].fa;
while (Tree[v].fa) v = Tree[v].fa;
return u == v;
}
int Query_deep(int x)
{//询问x到LCT根的距离(深度)
access(x);
Splay(x);
return Tree[x].siz;
}
}lct;
int main()
{
int m;
while (~scanf("%d", &lct.n))
{
lct.init(lct.n+);////让n+1表示被弹飞
for (int i = ; i <= lct.n; i++)
{
scanf("%d", &val[i]);
int id = min(i + val[i], lct.n + );
lct.link(i, id);
}
lct.mroot(lct.n + );
scanf("%d", &m);
for (int i = ; i <= m; i++)
{
int op, x;
scanf("%d%d", &op, &x);
x++;
if (op == ) printf("%d\n", lct.Query_deep(x)-);
else
{
int v;
scanf("%d", &v);
int oid = min(x + val[x], lct.n + );
int nid = min(x + v, lct.n + );
lct.cut(x, oid);
lct.link(x, nid);
lct.mroot(lct.n + );
val[x] = v;
}
}
}
return ;
}

3、魔法森林 HYSBZ - 3669

  题意:有一个无向图,起点在1,终点在n.每条边有两个权值ai、bi,当且仅当从1到n的路径过程中,身上a的数目不小于路径上的任一条边,b的数目也不小于路径上的任一条边。求最小的a+b。

  思路:先将所有边对ai从小到大排序,把所有边拆成两条边插入LCT,自身边权用新的点的点权表示。LCT中结点维护当前splay树中最大的bi以及对应的结点编号。各个结点的连通性用并查集维护。如果对于枚举到的边,其端点之间不连通,则将其拆成1个新的结点和两条边插入LCT;否则,得到该splay中最大的maxb(从splay的根结点得到),以及其对应的边所映射的点的编号(num),如果当前边的bi更小,则需要将原来的边删除,将该边插入(注意,因为把一条边拆成两条边,所以各需要操作两次)。判断结束后,如果当前情形下,起点和终点连通,则更新ans=min(ans,maxb+当前枚举边的ai)。

 #include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = + ;
const int maxm = + ;
const int INF = 0x7f7f7f7f;
struct EDGE
{
int from, to, a, b;
EDGE(int ff=,int tt=,int aa=,int bb=){}
friend bool operator <(const EDGE&e1, const EDGE&e2)
{
return e1.a < e2.a;
}
}edge[maxm];
int m,n;
//并查集,维护连通性
int pre[maxn + maxm];
int Find(int x)
{
if (pre[x] == x) return x;
else
{
int fa = pre[x];
pre[x] = Find(fa);
return pre[x];
}
} struct LCT
{
struct node
{
int fa, ch[]; //父亲(Splay对应的链向上由轻边连着哪个节点)、左右儿子
int reverse;//区间反转标记
bool is_root; //是否是所在Splay的根
int siz;//子树结点数目
int maxb, num, bi;//splay中最大的bi、与之对应的结点编号、当前结点的bi
}Tree[maxn+maxm]; void init(int MN)
{
for (int i = ; i <= MN; i++)
{
Tree[i].reverse = Tree[i].fa = Tree[i].ch[] = Tree[i].ch[] = ;
Tree[i].is_root = true;
Tree[i].num = i;
Tree[i].siz = ;
if (i <= n) Tree[i].maxb = Tree[i].bi = ;
else
{
Tree[i].maxb = Tree[i].bi = edge[i - n].b;
}
pre[i] = i;
}
} bool getson(int x)
{//x是否为重儿子
return x == Tree[Tree[x].fa].ch[];
}
bool isroot(int x)
{
return Tree[Tree[x].fa].ch[] != x && Tree[Tree[x].fa].ch[] != x;
}
void pushreverse(int x)
{
if (!x)return;
swap(Tree[x].ch[], Tree[x].ch[]);
Tree[x].reverse ^= ;
}
void pushdown(int x)
{//下传反转标记
if (Tree[x].reverse)
{
pushreverse(Tree[x].ch[]);
pushreverse(Tree[x].ch[]);
Tree[x].reverse = ;
}
} void update(int x)
{
int l = Tree[x].ch[], r = Tree[x].ch[];
Tree[x].siz = ;
Tree[x].maxb = Tree[x].bi;
Tree[x].num = x;
if (l)
{
Tree[x].siz += Tree[l].siz;
if (Tree[l].maxb > Tree[x].maxb) Tree[x].maxb = Tree[l].maxb, Tree[x].num = Tree[l].num;
}
if (r)
{
Tree[x].siz += Tree[r].siz;
if (Tree[r].maxb > Tree[x].maxb) Tree[x].maxb = Tree[r].maxb, Tree[x].num = Tree[r].num;
} } void rotate(int x)
{//将x旋转为根
if (Tree[x].is_root)return;
int k = getson(x), fa = Tree[x].fa;
int fafa = Tree[fa].fa;
pushdown(fa); pushdown(x); //先要下传标记
Tree[fa].ch[k] = Tree[x].ch[k ^ ];
if (Tree[x].ch[k ^ ])Tree[Tree[x].ch[k ^ ]].fa = fa;
Tree[x].ch[k ^ ] = fa;
Tree[fa].fa = x;
Tree[x].fa = fafa;
if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[]] = x;
else Tree[x].is_root = true, Tree[fa].is_root = false;
update(fa); update(x); //如果维护了信息,就要更新节点
}
void push(int x)
{
if (!Tree[x].is_root) push(Tree[x].fa);
pushdown(x);
}
int findroot(int x)
{//找到x在原树中的根节点
access(x); Splay(x);
pushdown(x);
while (Tree[x].ch[]) pushdown(x = Tree[x].ch[]);//找到深度最小的点即为根节点
return x;
}
void Splay(int x)
{//让x成为Splay的根,且x不含右儿子
push(x); //在Splay到根之前,必须先传完反转标记
for (int fa; !Tree[x].is_root; rotate(x)) {
if (!Tree[fa = Tree[x].fa].is_root) {
rotate((getson(x) == getson(fa)) ? fa : x);
}
}
}
void access(int x)
{//访问某节点。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。结束后x没有右儿子(没有深度比他大的点)
int y = ;
do {
Splay(x);
Tree[Tree[x].ch[]].is_root = true;
Tree[Tree[x].ch[] = y].is_root = false;
update(x); //如果维护了信息记得更新。
x = Tree[y = x].fa;
} while (x);
}
void mroot(int x)
{//把某个节点变成树根(这里的根指的是整棵LCT的根)
access(x);//使x与根结点处在同一棵splay中
Splay(x);//x成为这棵splay的根,x只有左儿子
//由于根节点所在的splay中,根节点没有左儿子(没有深度比他小的节点),将x的左右子树翻转
pushreverse(x);
}
void link(int u, int v)
{//连接u所在的LCT和v所在的LCT
mroot(u);//先让u成为其所在LCT的根
if (findroot(v) != u)Tree[u].fa = v;//如果u与v不在同一棵splay中,就把v设置为u的父亲
}
void cut(int u, int v)
{//分离出两棵LCT
mroot(u); //先让u成为其所在LCT的根
access(v); //让u和v在同一棵Splay中
Splay(v); //连接u、v,u是v的左儿子
pushdown(v); //先下传标记
if (Tree[v].ch[])
{
Tree[Tree[v].ch[]].fa = Tree[v].fa;
Tree[Tree[v].ch[]].is_root = true;
}
Tree[v].fa = ; Tree[v].ch[] = ;
//v的左孩子表示v上方相连的重链
update(v); //记得维护信息
} bool judge(int u, int v)
{//判断u和v是否连通
while (Tree[u].fa) u = Tree[u].fa;
while (Tree[v].fa) v = Tree[v].fa;
return u == v;
}
void split(int u, int v)
{//获取u->v的路径
mroot(u);//让u成为根结点
access(v);//访问v
Splay(v);//把v转到根结点,此时u的父亲为v
}
int Query_deep(int x)
{//询问x到LCT根的距离(深度)
access(x);
Splay(x);
return Tree[x].siz;
} }lct;
int main()
{
while (~scanf("%d%d", &n, &m))
{
for (int i = ; i <= m; i++)
{
scanf("%d%d%d%d", &edge[i].from, &edge[i].to, &edge[i].a, &edge[i].b);
}
sort(edge + , edge + + m);
lct.init(n+m); int ans = INF;
for (int i = ; i <= m; i++)
{
if (edge[i].from == edge[i].to)continue;
int f_from = Find(edge[i].from),f_to = Find(edge[i].to);
if (f_from != f_to)
{//不连通
lct.link(edge[i].from, i + n);//把一条边拆成2条边和一个点,化作点权
lct.link(edge[i].to, i + n);
pre[f_to] = pre[f_from] = Find(i + n);
}
else
{//连通
lct.split(edge[i].from, edge[i].to);
int x = lct.Tree[edge[i].to].num;
if (edge[i].b < lct.Tree[x].bi)
{
lct.cut(edge[i].from, x);
lct.cut(edge[i].to, x);
lct.link(edge[i].from, i + n);
lct.link(edge[i].to, i + n);
}
}
if (Find() == Find(n))
{
lct.split(, n);
ans = min(ans, lct.Tree[n].maxb + edge[i].a);
}
}
if (ans != INF) printf("%d\n", ans);
else printf("-1\n");
}
return ;
}

4、spoj 4155 OTOCI

  题意:有n座小岛,一开始两两之间没有桥,每座岛上有若干只企鹅。有三种操作:修改某座岛上的企鹅数目;将某两座岛相连;询问从一座岛到另一座岛路径上所有的企鹅数目。

  思路:用LCT维护某座岛的值和子树和。

 #include<iostream>
#include<cstdio>
using namespace std;
const int maxn = + ;
int n;
struct LCT
{
struct node
{
int fa, ch[]; //父亲(Splay对应的链向上由轻边连着哪个节点)、左右儿子
int reverse;//区间反转标记
bool is_root; //是否是所在Splay的根
int val;
int sum;
}Tree[maxn]; void init(int MN)
{
for (int i = ; i <= MN; i++)
{
Tree[i].reverse = Tree[i].fa = Tree[i].ch[] = Tree[i].ch[] = ;
Tree[i].is_root = true;
}
} bool getson(int x)
{//x是否为重儿子
return x == Tree[Tree[x].fa].ch[];
}
bool isroot(int x)
{
return Tree[Tree[x].fa].ch[] != x && Tree[Tree[x].fa].ch[] != x;
}
void pushreverse(int x)
{
if (!x)return;
swap(Tree[x].ch[], Tree[x].ch[]);
Tree[x].reverse ^= ;
}
void pushdown(int x)
{//下传反转标记
if (Tree[x].reverse)
{
pushreverse(Tree[x].ch[]);
pushreverse(Tree[x].ch[]);
Tree[x].reverse = ;
}
} void update(int x)
{
int l = Tree[x].ch[], r = Tree[x].ch[];
Tree[x].sum = Tree[x].val;
if (l) Tree[x].sum += Tree[l].sum;
if (r) Tree[x].sum += Tree[r].sum;
} void rotate(int x)
{//将x旋转为根
if (Tree[x].is_root)return;
int k = getson(x), fa = Tree[x].fa;
int fafa = Tree[fa].fa;
pushdown(fa); pushdown(x); //先要下传标记
Tree[fa].ch[k] = Tree[x].ch[k ^ ];
if (Tree[x].ch[k ^ ])Tree[Tree[x].ch[k ^ ]].fa = fa;
Tree[x].ch[k ^ ] = fa;
Tree[fa].fa = x;
Tree[x].fa = fafa;
if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[]] = x;
else Tree[x].is_root = true, Tree[fa].is_root = false;
update(fa); update(x); //如果维护了信息,就要更新节点
}
void push(int x)
{
if (!Tree[x].is_root) push(Tree[x].fa);
pushdown(x);
}
int findroot(int x)
{//找到x在原树中的根节点
access(x); Splay(x);
pushdown(x);
while (Tree[x].ch[]) pushdown(x = Tree[x].ch[]);//找到深度最小的点即为根节点
return x;
}
void Splay(int x)
{//让x成为Splay的根,且x不含右儿子
push(x); //在Splay到根之前,必须先传完反转标记
for (int fa; !Tree[x].is_root; rotate(x)) {
if (!Tree[fa = Tree[x].fa].is_root) {
rotate((getson(x) == getson(fa)) ? fa : x);
}
}
}
void access(int x)
{//访问某节点。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。结束后x没有右儿子(没有深度比他大的点)
int y = ;
do {
Splay(x);
Tree[Tree[x].ch[]].is_root = true;
Tree[Tree[x].ch[] = y].is_root = false;
update(x); //如果维护了信息记得更新。
x = Tree[y = x].fa;
} while (x);
}
void mroot(int x)
{//把某个节点变成树根(这里的根指的是整棵LCT的根)
access(x);//使x与根结点处在同一棵splay中
Splay(x);//x成为这棵splay的根,x只有左儿子
//由于根节点所在的splay中,根节点没有左儿子(没有深度比他小的节点),将x的左右子树翻转
pushreverse(x);
}
void link(int u, int v)
{//连接u所在的LCT和v所在的LCT
mroot(u);//先让u成为其所在LCT的根
if (findroot(v) != u)Tree[u].fa = v;//如果u与v不在同一棵splay中,就把v设置为u的父亲 }
void cut(int u, int v)
{//分离出两棵LCT
mroot(u); //先让u成为其所在LCT的根
access(v); //让u和v在同一棵Splay中
Splay(v); //连接u、v,u是v的左儿子
pushdown(v); //先下传标记
if (Tree[v].ch[])
{
Tree[Tree[v].ch[]].fa = Tree[v].fa;
Tree[Tree[v].ch[]].is_root = true;
}
Tree[v].fa = ; Tree[v].ch[] = ;
//v的左孩子表示v上方相连的重链
update(v); //记得维护信息
} bool judge(int u, int v)
{//判断u和v是否连通
while (Tree[u].fa) u = Tree[u].fa;
while (Tree[v].fa) v = Tree[v].fa;
return u == v;
}
void split(int u, int v)
{//获取u->v的路径
mroot(u);//让u成为根结点
access(v);//访问v
Splay(v);//把v转到根结点,此时u的父亲为v,u是v的左儿子
} void modify(int x, int v)
{//改变点值
access(x);
Splay(x);
Tree[x].val = v;
update(x);
} }lct;
int main()
{
scanf("%d", &n);
lct.init(n);
for (int i = ; i <= n; i++)
{
scanf("%d", &lct.Tree[i].val);
lct.Tree[i].sum = lct.Tree[i].val;
}
int m;
scanf("%d", &m);
char op[];
while (m--)
{
scanf("%s", op);
if (op[] == 'e')
{
int u, v;
scanf("%d%d", &u, &v);
if (lct.judge(u, v))
{
lct.split(u, v);
printf("%d\n", lct.Tree[v].sum );
}
else
{
printf("impossible\n");
}
}
else if (op[] == 'b')
{
int u, v;
scanf("%d%d", &u, &v);
if (!lct.judge(u, v))
{
printf("yes\n");
lct.link(u, v);
}
else printf("no\n");
}
else
{
int u, w;
scanf("%d%d", &u, &w);
lct.modify(u, w);
}
}
return ;
}

5、Caves and Tunnels URAL - 1553

  题意:有n个洞穴,由n-1条通道相连。有两种操作:将某个洞穴的辐射水平升高一定值;询问两个洞穴之间最大的辐射值。

  思路:LCT维护当前结点的辐射值以及子树的最大辐射值。亦可以使用树链剖分来解决,因为没有链的变动,其实是个静态树。

 #include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn = + ;
int n, q;
struct LCT
{
struct node
{
int fa, ch[]; //父亲(Splay对应的链向上由轻边连着哪个节点)、左右儿子
int reverse;//区间反转标记
bool is_root; //是否是所在Splay的根
int val;
int maxv;
}Tree[maxn]; void init(int MN)
{
for (int i = ; i <= MN; i++)
{
Tree[i].reverse = Tree[i].fa = Tree[i].ch[] = Tree[i].ch[] = ;
Tree[i].is_root = true;
Tree[i].val = Tree[i].maxv = ;
}
} bool getson(int x)
{//x是否为重儿子
return x == Tree[Tree[x].fa].ch[];
}
bool isroot(int x)
{
return Tree[Tree[x].fa].ch[] != x && Tree[Tree[x].fa].ch[] != x;
}
void pushreverse(int x)
{
if (!x)return;
swap(Tree[x].ch[], Tree[x].ch[]);
Tree[x].reverse ^= ;
}
void pushdown(int x)
{//下传反转标记
if (Tree[x].reverse)
{
pushreverse(Tree[x].ch[]);
pushreverse(Tree[x].ch[]);
Tree[x].reverse = ;
}
} void update(int x)
{
int l = Tree[x].ch[], r = Tree[x].ch[];
Tree[x].maxv = Tree[x].val;
if (l) Tree[x].maxv = max(Tree[x].maxv, Tree[l].maxv);
if (r) Tree[x].maxv = max(Tree[x].maxv, Tree[r].maxv);
} void rotate(int x)
{//将x旋转为根
if (Tree[x].is_root)return;
int k = getson(x), fa = Tree[x].fa;
int fafa = Tree[fa].fa;
pushdown(fa); pushdown(x); //先要下传标记
Tree[fa].ch[k] = Tree[x].ch[k ^ ];
if (Tree[x].ch[k ^ ])Tree[Tree[x].ch[k ^ ]].fa = fa;
Tree[x].ch[k ^ ] = fa;
Tree[fa].fa = x;
Tree[x].fa = fafa;
if (!Tree[fa].is_root)Tree[fafa].ch[fa == Tree[fafa].ch[]] = x;
else Tree[x].is_root = true, Tree[fa].is_root = false;
update(fa); update(x); //如果维护了信息,就要更新节点
}
void push(int x)
{
if (!Tree[x].is_root) push(Tree[x].fa);
pushdown(x);
}
int findroot(int x)
{//找到x在原树中的根节点
access(x); Splay(x);
pushdown(x);
while (Tree[x].ch[]) pushdown(x = Tree[x].ch[]);//找到深度最小的点即为根节点
return x;
}
void Splay(int x)
{//让x成为Splay的根,且x不含右儿子
push(x); //在Splay到根之前,必须先传完反转标记
for (int fa; !Tree[x].is_root; rotate(x)) {
if (!Tree[fa = Tree[x].fa].is_root) {
rotate((getson(x) == getson(fa)) ? fa : x);
}
}
}
void access(int x)
{//访问某节点。作用是:对于访问的节点x,打通一条从树根(真实的LCT树)到x的重链;如果x往下是重链,那么把x往下的重边改成轻边。结束后x没有右儿子(没有深度比他大的点)
int y = ;
do {
Splay(x);
Tree[Tree[x].ch[]].is_root = true;
Tree[Tree[x].ch[] = y].is_root = false;
update(x); //如果维护了信息记得更新。
x = Tree[y = x].fa;
} while (x);
}
void mroot(int x)
{//把某个节点变成树根(这里的根指的是整棵LCT的根)
access(x);//使x与根结点处在同一棵splay中
Splay(x);//x成为这棵splay的根,x只有左儿子
//由于根节点所在的splay中,根节点没有左儿子(没有深度比他小的节点),将x的左右子树翻转
pushreverse(x);
}
void link(int u, int v)
{//连接u所在的LCT和v所在的LCT
mroot(u);//先让u成为其所在LCT的根
if (findroot(v) != u)Tree[u].fa = v;//如果u与v不在同一棵splay中,就把v设置为u的父亲
}
void cut(int u, int v)
{//分离出两棵LCT
mroot(u); //先让u成为其所在LCT的根
access(v); //让u和v在同一棵Splay中
Splay(v); //连接u、v,u是v的左儿子
pushdown(v); //先下传标记
if (Tree[v].ch[])
{
Tree[Tree[v].ch[]].fa = Tree[v].fa;
Tree[Tree[v].ch[]].is_root = true;
}
Tree[v].fa = ; Tree[v].ch[] = ;
//v的左孩子表示v上方相连的重链
update(v); //记得维护信息
} bool judge(int u, int v)
{//判断u和v是否连通
while (Tree[u].fa) u = Tree[u].fa;
while (Tree[v].fa) v = Tree[v].fa;
return u == v;
}
void split(int u, int v)
{//获取u->v的路径
mroot(u);//让u成为根结点
access(v);//访问v
Splay(v);//把v转到根结点,此时u的父亲为v
} void modify(int x, int v)
{//改变点值
access(x);
Splay(x);
Tree[x].val += v;
update(x); } }lct;
char op[];
int main()
{
scanf("%d", &n);
lct.init(n);
for (int i = ; i <= n - ; i++)
{
int u, v;
scanf("%d%d", &u, &v);
lct.link(u, v);
}
scanf("%d", &q);
while (q--)
{
scanf("%s", op);
if (op[] == 'I')
{
int u, w;
scanf("%d%d", &u, &w);
lct.modify(u, w);
}
else
{
int u, v;
scanf("%d%d", &u, &v);
lct.split(u, v);
printf("%d\n", lct.Tree[v].maxv);
}
}
return ;
}

LCT(link cut tree) 动态树的更多相关文章

  1. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

  2. Link Cut Tree 动态树 小结

    动态树有些类似 树链剖分+并查集 的思想,是用splay维护的 lct的根是动态的,"轻重链"也是动态的,所以并没有真正的轻重链 动态树的操作核心是把你要把 修改/询问/... 等 ...

  3. 洛谷.3690.[模板]Link Cut Tree(动态树)

    题目链接 LCT(良心总结) #include <cstdio> #include <cctype> #include <algorithm> #define gc ...

  4. 洛谷P3690 Link Cut Tree (动态树)

    干脆整个LCT模板吧. 缺个链上修改和子树操作,链上修改的话join(u,v)然后把v splay到树根再打个标记就好. 至于子树操作...以后有空的话再学(咕咕咕警告) #include<bi ...

  5. 【学习笔记】LCT link cut tree

    大概就是供自己复习的吧 1. 细节讲解 安利两篇blog: Menci 非常好的讲解与题单 2.模板 把 $ rev $ 和 $ pushdown $ 的位置记清 #define lc son[x][ ...

  6. 【刷题】洛谷 P3690 【模板】Link Cut Tree (动态树)

    题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor ...

  7. LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板

    P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...

  8. P3690 【模板】Link Cut Tree (动态树)

    P3690 [模板]Link Cut Tree (动态树) 认父不认子的lct 注意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> v ...

  9. LG3690 【模板】Link Cut Tree (动态树)

    题意 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和.保证x到y是联通的 ...

随机推荐

  1. 两款 REST 测试工具

    用CURL命令行测试REST API 无疑是低效率的,这里把最近使用的两款 Chrome 插件总结下 POSTMAN 简单易用 REST Console 功能强大 使用的话用POSTMAN就够用了,但 ...

  2. MySQL Migration Toolkit启动报jre错误

    正常启动的话,会报出“an error occurred during the initialization of the runtime system.Please make sure you ha ...

  3. XML简单学习

    XML简单概述 1.Extensible Markup language可扩展标记语言; 2.作用:具有层次性的描述有关系的数据: 体现在:描述数据关系:软件配置,以描述程序模块之间的关系: 语法介绍 ...

  4. 隐式意图调用系统自带组件的各种Uri总结

    调用系统应用解析(必需要加各自使用的权限)  android intent 隐式意图和显示意图(activity跳转) 显示意图要求必须知道被激活组件的包和class 隐式意图仅仅须要知道跳转acti ...

  5. cocos2dx lua 3.10 使用cjson

    本篇介绍如何在lua中使用cjson对数据进行json的encode与decode,首先简单介绍下cjson: Lua CJSON 是 Lua 语言提供高性能的 JSON 解析器和编码器,其性能比纯 ...

  6. 【ask】vc11 sln文件里加入新的vcxproj已有vcxproj里的用户宏没有自动复制过来

    今天在quick-cocos2d-x生成的sln,需要添加一个新的自己的lib,以前就是直接把.h和.cc文件直接添加过去(为了省事呗),今天时间宽裕索性还是新建一个vcxproj吧,然后用静态lib ...

  7. MVC结构简介

    本文编译自J2EE的相关文档.MVC(Model-View-Controller)应用程序结构被用来分析分布式应用程序的特征.这种抽象结构能有助于将应用程序分割成若干逻辑部件,使程序设计变得更加容易. ...

  8. zookeeper Java API 简单操作示例

    本文主要介绍如何在java IDE中如何应用使用客户端与zookeeper服务器通信. 首先搭建maven环境,并在pom文件中加入zookeeper引用包: <!-- https://mvnr ...

  9. bootstrap Table API和一些简单使用方法

    官网: http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/ 后端分页问题:后端返回”rows”.“”total,这样才能重新赋值 ...

  10. angular下如何绑定form表单的change事件

    ng-change = "formChange()" 不起作用 应该改为: onchanged = "angular.element(this).scope().form ...