题面
思路
$LCT$
我们发现,这个1操作,好像非常像$LCT$里面的$Access$啊~
那么我们尝试把$Access$操作魔改成本题中的涂色
我们令$LCT$中的每一个$splay$链代表同一种颜色的一条链,那么$Access(u)$就相当于把这一段变成同一种颜色
注意这个东西能成立,是因为每次涂上的都是新的一种颜色(所以如果有$m$种颜色,每次涂其中一种,可能重复的之类的就不能这么做了)
线段树
接下来我们解决询问的问题:什么结构能维护链上信息和子树信息(同时)?当然是线段树了~
我们考虑开一棵以$dfs$序为下标的线段树,线段树的每个叶节点保存这个节点到根的路径权值,其他节点维护对应区间的最大值
这样,询问三就变成了区间求$max$,询问二则可以化成$w[u]+w[v]-2*w[lca]+1$这样的形式($w[u]$表示$u$到根路径上的权值)(这个东西不管颜色怎么分布,一定是对的,证明很容易,可以自己想想)
但是这样之后,我们在修改一的时候,怎么修改线段树上的值呢?
维护线段树
我们发现,在修改的过程中,每一次我们从当前$splay$连向上面的$splay$时,会把一条虚边变成重边、一条重边变成虚边
那么,原来重边下的这棵子树,因为它上面的东西变成了新的颜色,而上面的东西本来是和它一个颜色的,所以重边的这棵子树中所有节点的权值要+1
而对于原来虚边下面的这棵子树来说,它上面的东西本来和它不是一个颜色的,现在是同一个颜色了,所以虚边的子树中所有节点的权值要-1
这样,我们只要在$Access$的时候,同时维护线段树的权值就可以了
总结
本题从与$Access$相似的修改方式开始,切入点是$LCT$,然后加入了线段树维护,并且确定了在$Access$操作的同时修改线段树的值
总时间复杂度为均摊$O(nlog^2n)$
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define ll long long
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
int n,Q,first[100010],cnte,dep[100010],st[100010][20],dfn[100010],lim[100010],back[100010],cntn;
int fa[100010];
struct edge{
int to,next;
}e[200010];
inline void add(int u,int v){
e[++cnte]=(edge){v,first[u]};first[u]=cnte;
e[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
void dfs(int u,int f){
int i,v;dep[u]=dep[f]+1;st[u][0]=f;fa[u]=f;//这里把LCT的fa数组预处理好(此时没有边,都是虚边)
dfn[u]=++cntn;back[cntn]=u;
for(i=first[u];~i;i=e[i].next){
v=e[i].to;if(v==f) continue;
dfs(v,u);
}
lim[u]=cntn;
}
void ST(){
for(int j=1;j<=19;j++){
for(int i=1;i<=n;i++) st[i][j]=st[st[i][j-1]][j-1];
}
}
int lca(int l,int r){
int i;
if(dep[l]>dep[r]) swap(l,r);
for(i=19;i>=0;i--) if(dep[st[r][i]]>=dep[l]) r=st[r][i];
if(l==r) return l;
for(i=19;i>=0;i--){
if(st[r][i]!=st[l][i]){
l=st[l][i];
r=st[r][i];
}
}
return st[l][0];
}
//segment tree
int a[400010],lazy[400010];
void update(int num){
a[num]=max(a[num<<1],a[num<<1|1]);
}
void push(int l,int r,int num){
if(l==r||!lazy[num]) return;
a[num<<1]+=lazy[num];a[num<<1|1]+=lazy[num];
lazy[num<<1]+=lazy[num];lazy[num<<1|1]+=lazy[num];
lazy[num]=0;
}
void build(int l,int r,int num){
if(l==r){a[num]=dep[back[l]];return;}
int mid=(l+r)>>1;
build(l,mid,num<<1);build(mid+1,r,num<<1|1);
update(num);
}
void change(int l,int r,int ql,int qr,int num,int ch){
push(l,r,num);
if(l>=ql&&r<=qr){a[num]+=ch;lazy[num]+=ch;return;}
int mid=(l+r)>>1;
if(mid>=ql) change(l,mid,ql,qr,num<<1,ch);
if(mid<qr) change(mid+1,r,ql,qr,num<<1|1,ch);
update(num);
}
int query(int l,int r,int ql,int qr,int num){
push(l,r,num);
if(l>=ql&&r<=qr) return a[num];
int mid=(l+r)>>1,re=-1e9;
if(mid>=ql) re=max(re,query(l,mid,ql,qr,num<<1));
if(mid<qr) re=max(re,query(mid+1,r,ql,qr,num<<1|1));
return re;
}
//link cut tree
int ch[100010][2]={0},rt[100010];
int get(int pos){return ch[fa[pos]][1]==pos;}
void rotate(int x){
int f=fa[x],ff=fa[f],son=get(x);
ch[f][son]=ch[x][son^1];
if(ch[f][son]) fa[ch[f][son]]=f;
fa[f]=x;ch[x][son^1]=f;
fa[x]=ff;
if(rt[f]) rt[x]=1,rt[f]=0;
else ch[ff][ch[ff][1]==f]=x;
}
void splay(int pos){
if(rt[pos]) return;
for(int f;!rt[pos];rotate(pos)){
if(!rt[f=fa[pos]])
rotate((get(f)==get(pos))?f:pos);
}
}
int pre(int pos){
while(ch[pos][0]) pos=ch[pos][0];
return pos;
}
void access(int pos){
for(int tmp=0,tt;pos;tmp=pos,pos=fa[pos]){
splay(pos);
if(ch[pos][1]){//重边变虚边,+1
tt=pre(ch[pos][1]);
change(1,n,dfn[tt],lim[tt],1,1);
}
rt[ch[pos][1]]=1;ch[pos][1]=tmp;rt[tmp]=0;
if(tmp){//虚边变重边,-1
tt=pre(tmp);
change(1,n,dfn[tt],lim[tt],1,-1);
}
}
}
int main(){
memset(first,-1,sizeof(first));
n=read();Q=read();int i,t1,t2,t3,f;
for(i=1;i<n;i++){
t1=read();t2=read();
add(t1,t2);
}
dfs(1,0);ST();build(1,n,1);
for(i=1;i<=n;i++) rt[i]=1;
while(Q--){
t1=read();
if(t1==1){
t2=read();access(t2);
}
if(t1==2){
t2=read();t3=read();
f=lca(t2,t3);
printf("%d
",query(1,n,dfn[t2],dfn[t2],1)+query(1,n,dfn[t3],dfn[t3],1)-2*query(1,n,dfn[f],dfn[f],1)+1);
}
if(t1==3){
t2=read();
printf("%d
",query(1,n,dfn[t2],lim[t2],1));
}
}
}