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

时间:2022-11-04 21:35:44

UPD:更新了写法。

【模板】Link Cut Tree

给定n个点以及每个点的权值,要你处理接下来的m个操作。操作有4种。操作从0到3编号。点从1到n编号。

  1. 后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。保证x到y是联通的。

  2. 后接两个整数(x,y),代表连接x到y,若x到y已经联通则无需连接。

  3. 后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。

  4. 后接两个整数(x,y),代表将点x上的权值变成y。

数据范围: \(1 \leq N, M \leq 3 \cdot {10}^5\)

http://www.cnblogs.com/flashhu/p/8324551.html

https://www.cnblogs.com/candy99/p/6271344.html

时间复杂度\(O(n+m \log n)\)

CO int N=100000+10;
int fa[N],ch[N][2],rev[N];
int val[N],sum[N]; #define lc ch[x][0]
#define rc ch[x][1]
IN bool nroot(int x){
return x==ch[fa[x]][0] or x==ch[fa[x]][1];
}
IN void push_up(int x){
sum[x]=sum[lc]^val[x]^sum[rc];
}
IN void push_down(int x){
if(rev[x]){
swap(lc,rc);
rev[lc]^=1,rev[rc]^=1;
rev[x]=0;
}
}
IN void rotate(int x){
int y=fa[x],z=fa[y],l=x==ch[y][1],r=l^1;
if(nroot(y)) ch[z][y==ch[z][1]]=x;fa[x]=z;
ch[y][l]=ch[x][r],fa[ch[x][r]]=y;
ch[x][r]=y,fa[y]=x;
push_up(y);
}
void splay(int x){
vector<int> stk(1,x);
for(int i=x;nroot(i);) stk.push_back(i=fa[i]);
for(;stk.size();stk.pop_back()) push_down(stk.back());
for(;nroot(x);rotate(x)){
int y=fa[x],z=fa[y];
if(nroot(y)) rotate((x==ch[y][1])!=(y==ch[z][1])?x:y);
}
push_up(x);
}
void access(int x){
for(int y=0;x;y=x,x=fa[x])
splay(x),rc=y,push_up(x);
}
void make_root(int x){
access(x),splay(x),rev[x]^=1;
}
int find_root(int x){
access(x),splay(x);
while(lc) x=lc;
splay(x);
return x;
}
#undef lc
#undef rc int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) read(val[i]);
while(m--)switch(read<int>()){
case 0:{
int x=read<int>(),y=read<int>();
make_root(x),access(y),splay(y);
printf("%d\n",sum[y]);
break;
}
case 1:{
int x=read<int>(),y=read<int>();
make_root(x);
if(find_root(y)!=x) fa[x]=y;
break;
}
case 2:{
int x=read<int>(),y=read<int>();
make_root(x);
if(find_root(y)==x and fa[y]==x and !ch[y][0]){
fa[y]=ch[x][1]=0;
push_up(x);
}
break;
}
default:{
int x=read<int>();
splay(x),read(val[x]),push_up(x);
break;
}
}
return 0;
}

洞穴勘测

辉辉热衷于洞穴勘测。某天,他按照地图来到了一片被标记为JSZX的洞穴群地区。经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴。假如两个洞穴可以通过一条或者多条通道按一定顺序连接起来,那么这两个洞穴就是连通的,按顺序连接在一起的这些通道则被称之为这两个洞穴之间的一条路径。洞穴都十分坚固无法破坏,然而通道不太稳定,时常因为外界影响而发生改变,比如,根据有关仪器的监测结果,123号洞穴和127号洞穴之间有时会出现一条通道,有时这条通道又会因为某种稀奇古怪的原因被毁。

辉辉有一台监测仪器可以实时将通道的每一次改变状况在辉辉手边的终端机上显示:

  • 如果监测到洞穴u和洞穴v之间出现了一条通道,终端机上会显示一条指令 Connect u v

  • 如果监测到洞穴u和洞穴v之间的通道被毁,终端机上会显示一条指令 Destroy u v

经过长期的艰苦卓绝的手工推算,辉辉发现一个奇怪的现象:无论通道怎么改变,任意时刻任意两个洞穴之间至多只有一条路径。因而,辉辉坚信这是由于某种本质规律的支配导致的。因而,辉辉更加夜以继日地坚守在终端机之前,试图通过通道的改变情况来研究这条本质规律。然而,终于有一天,辉辉在堆积成山的演算纸中崩溃了……他把终端机往地面一砸(终端机也足够坚固无法破坏),转而求助于你,说道:“你老兄把这程序写写吧”。

辉辉希望能随时通过终端机发出指令 Query u v,向监测仪询问此时洞穴u和洞穴v是否连通。现在你要为他编写程序回答每一次询问。已知在第一条指令显示之前,JSZX洞穴群中没有任何通道存在。

100%的数据满足n≤10000, m≤200000

保证所有Destroy指令将摧毁的是一条存在的通道

LCT模板题,总结了一下我所见过的最简洁的写法。时间复杂度\(O(m \log n)\)

co int N=1e4+1;
namespace T{
int fa[N],ch[N][2],st[N],rev[N];
#define lc ch[x][0]
#define rc ch[x][1]
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void pushdown(int x){
if(rev[x]){
std::swap(lc,rc);
rev[lc]^=1,rev[rc]^=1;
rev[x]=0;
}
}
void rotate(int x){
int y=fa[x],z=fa[y],l=x==ch[y][1],r=l^1;
if(nroot(y)) ch[z][y==ch[z][1]]=x;fa[x]=z;
ch[y][l]=ch[x][r],fa[ch[x][r]]=y;
ch[x][r]=y,fa[y]=x;
}
void splay(int x){
int y=x,z=0;
st[++z]=y;
while(nroot(y)) st[++z]=y=fa[y];
while(z) pushdown(st[z--]);
for(;nroot(x);rotate(x)){
y=fa[x],z=fa[y];
if(nroot(y)) rotate(y==ch[z][1]^x==ch[y][1]?x:y);
}
}
void access(int x){
for(int y=0;x;x=fa[y=x])
splay(x),rc=y;
}
void makeroot(int x){
access(x),splay(x),rev[x]^=1;
}
int findroot(int x){
access(x),splay(x);
while(lc) x=lc;
return x;
}
void link(int x,int y){
makeroot(x),fa[x]=y;
}
void cut(int x,int y){
makeroot(x),access(y),splay(y),ch[y][0]=fa[x]=0;
}
}
int main(){
int n=read<int>(),m=read<int>();
char opt[10];
int x,y;
while(m--){
scanf("%s",opt);
read(x),read(y);
if(opt[0]=='C') T::link(x,y);
else if(opt[0]=='D') T::cut(x,y);
else puts(T::findroot(x)==T::findroot(y)?"Yes":"No");
}
return 0;
}