$LCT$维护树上联通块。
首先看$1$操作,起点为根的链染色,而且是chuan全新的颜色。可以自己画个图看看。
是不是有种$LCT$里的$access$的感觉?那就可以考虑用$LCT$维护树的形态,把相同颜色的点放在同一棵$splay$里。因为一定是从根开始的染色,染的是不同的颜色,初始颜色又不同,就能保证一棵$splay$一定维护一条链,符合$LCT$的性质,是可行的。(事实上这也是$LCT$维护树上联通块的常见姿势)
于是对于操作$1$,就是$access$一下。对于一个点的权值也就是从该点到根节点的路径会经过几个$splay$。因为在$access$时,每向上跳一次虚边就相当于跨过了一个$splay$,所以可以用这个性质来统计。
伪代码:
int fake_access(int x){
int ans=0;
for(;x;x=fa[x],++ans)
splay(x);
//统计答案不能真的access改变树的形态,所以不连儿子
//这也是为啥叫fAKe_access——它没有真的access
return ans;
}
于是操作$2$就是$fakeaccess(x)+fakeaccess(y)-2*fakeaccess(lca(x,y))+1$
(一定要$+1$,因为减去了$2*fakeaccess(lca(x,y))$后,$lca(x,y)$自己的$splay$就被减没了)
麻烦的是操作$3$。
根据上面,一个点的权值,也就相当于一个点到根节点路径上有几条虚边。
那么有一条虚边变重了,它对应的子树权值就会都$-1$;有一条实边变轻了,它对应的子树权值都会$+1$。
初始权值好求,就是点的深度。子树加加减减,还要求子树权值$max$,可以用$dfs$序+线段树来维护。
能改变虚实边的就是$access$操作了。这里还要在$LCT$中要维护一下每个$splay$中深度最小的点(记为$ma[x]$,原因看下面伪代码解释)。
伪代码:
//seg为dfs序,siz为子树大小
void access(int x){
for(int y=0;x;y=x,x=fa[x]){
splay(x);
if(son(x,1))st.add(seg[ma[son(x,1)]],seg[ma[son(x,1)]]+siz[ma[son(x,1)]]-1,1,cnt,1,1);
//son(x,1)变轻,子树+1
//变轻的一定是这棵splay中深度最小的点(毕竟是一条链),但是本身在splay中可能会出现它的根不是深度最小的点,所以要维护一下
if(y)st.add(seg[ma[y]],seg[ma[y]]+siz[ma[y]]-1,1,cnt,1,-1);
//y变重,子树-1
//同理,变重的也是深度最小的点
son(x,1)=y;
update(x);
}
}
这样就实现了用$LCT$维护形态,在线段树上维护信息。其实这样操作$2$也可以用线段树来查了。
时间复杂度:$O(nlog^2n)$
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <set>
#define maxn 100005
#define inf 0x3f3f3f
#define pn putchar('\n')
#define px(x) putchar(x)
#define ps putchar(' ')
#define pd puts("======================")
#define pj puts("++++++++++++++++++++++")
using namespace std;
inline int read(){
int x=0,y=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return y?-x:x;
}
template<typename T>
inline T read(){
T x=0;
int y=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return y?-x:x;
}
inline int ran(){
static int seed=45562;
return seed=(seed*48271LL%2147483647);
}
int h[maxn],num;
struct edge{
int to,pre;
}e[maxn<<1];
inline void add(int from,int to){
e[++num].pre=h[from],h[from]=num,e[num].to=to;
}
int pos[maxn],seg[maxn],top[maxn],deep[maxn],fa[maxn],son[maxn],siz[maxn],cnt;
struct Segment_Tree{
int ma[maxn<<2],tag[maxn<<2];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
inline void update(int node){
ma[node]=max(ma[ls(node)],ma[rs(node)]);
}
inline void datadown(int node,int d){
ma[node]+=d;
tag[node]+=d;
}
inline void pushdown(int node){
datadown(ls(node),tag[node]);
datadown(rs(node),tag[node]);
tag[node]=0;
}
void build(int l,int r,int node){
if(l==r){
ma[node]=deep[pos[l]]+1;
//因为深度是从0开始的,所以要+1
return;
}
int mid=l+r>>1;
build(l,mid,ls(node));
build(mid+1,r,rs(node));
update(node);
}
void add(int L,int R,int l,int r,int node,int d){
if(L<=l&&R>=r){
datadown(node,d);
return;
}
if(tag[node])pushdown(node);
int mid=l+r>>1;
if(L<=mid)add(L,R,l,mid,ls(node),d);
if(R>mid)add(L,R,mid+1,r,rs(node),d);
update(node);
}
int ask(int L,int R,int l,int r,int node){
if(L<=l&&R>=r)return ma[node];
if(tag[node])pushdown(node);
int mid=l+r>>1,ans=0;
if(L<=mid)ans=ask(L,R,l,mid,ls(node));
if(R>mid)ans=max(ans,ask(L,R,mid+1,r,rs(node)));
return ans;
}
}st;//线段树
struct Link_Cut_Tree{
int son[maxn][2],fa[maxn],ma[maxn];
#define son(x,y) son[x][y]
#define whson(x) (son[fa[x]][1]==x)
#define root(x) (son[fa[x]][0]!=x&&son[fa[x]][1]!=x)
inline void update(int node){
if(son(node,0))ma[node]=ma[son(node,0)];
else ma[node]=node;
}
inline void addedge(int s,int f,int wh){
son(f,wh)=s;
if(s)fa[s]=f;
}
inline void zhuan(int x){
int f=fa[x],gf=fa[f],wh=whson(x);
fa[x]=gf;
if(!root(f))son(gf,whson(f))=x;
addedge(son(x,wh^1),f,wh);
addedge(f,x,wh^1);
update(f),update(x);
}
void splay(int x){
int y=fa[x];
while(!root(x)){
if(!root(y))
zhuan(whson(x)^whson(y)?x:y);
zhuan(x);
y=fa[x];
}
}
void access(int x){
for(int y=0;x;y=x,x=fa[x]){
splay(x);
if(son(x,1))st.add(seg[ma[son(x,1)]],seg[ma[son(x,1)]]+siz[ma[son(x,1)]]-1,1,cnt,1,1);
if(y)st.add(seg[ma[y]],seg[ma[y]]+siz[ma[y]]-1,1,cnt,1,-1);
son(x,1)=y;
update(x);
}
}
int fake_access(int x){
int ans=0;
for(;x;x=fa[x],++ans)
splay(x);
return ans;
}
}lct;//lct
struct Tree_Chain_Split{
void dfs1(int node=1){
siz[node]=1;
lct.ma[node]=node;
for(register int i=h[node];i;i=e[i].pre){
int x=e[i].to;
if(!siz[x]){
lct.fa[x]=fa[x]=node;
deep[x]=deep[node]+1;
dfs1(x);
if(siz[x]>siz[son[node]])son[node]=x;
siz[node]+=siz[x];
}
}
}
void dfs2(int node=1){
seg[node]=++cnt;
pos[cnt]=node;
if(son[node]){
top[son[node]]=top[node];
dfs2(son[node]);
for(register int i=h[node];i;i=e[i].pre){
int x=e[i].to;
if(!seg[x])top[x]=x,dfs2(x);
}
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]])swap(x,y);
x=fa[top[x]];
}
return deep[x]<deep[y]?x:y;
}//用的树剖求lca
}tcs;
int main(){
int n=read(),m=read();
for(register int i=1;i<n;++i){
int a=read(),b=read();
add(a,b),add(b,a);
}
tcs.dfs1(),tcs.dfs2();
st.build(1,cnt,1);
while(m--){
int s=read(),x=read();
if(s==1)lct.access(x);
else if(s==2){
int y=read();
printf("%d\n",lct.fake_access(x)+lct.fake_access(y)-(lct.fake_access(tcs.lca(x,y))<<1)+1);
}
else printf("%d\n",st.ask(seg[x],seg[x]+siz[x]-1,1,cnt,1));
}
}