传送门
本题的做法非常神奇,原理的思想需要仔细体会才能理解,这里只陈述客观的做法,原因不解释(太难解释了。
首先发现 2 2 2号操作可以放到最后处理,其次 0 0 0号操作可以认为是对所有的树进行一次统一的操作,因为 2 2 2号操作不会询问不存在树上的点,而 1 1 1号操作某个节点的时候需要与创建该节点的 0 0 0号操作的范围取一下交集。
考虑将操作离线处理,对于所有的
1
1
1号类型的操作都建立一个虚点,假设编号是
v
1
,
v
2
,
v
3
,
.
.
.
,
v
k
v_1,v_2,v_3,...,v_k
v1,v2,v3,...,vk,其中
k
k
k代表
1
1
1号类型操作的数目,然后建立
1
←
v
1
←
v
2
←
v
3
.
.
.
←
v
k
1\leftarrow v_1 \leftarrow v_2 \leftarrow v_3...\leftarrow v_k
1←v1←v2←v3...←vk的一条链,箭头指向的是父亲节点。
现在将所有的实点都连向向前离生成它的时间最近的操作
1
1
1对应的虚点,这样能够形成一个以
1
1
1为根节点的树,其中虚点的作用在于确定连向它的所有实点的父亲节点。
现在我们考虑相邻两颗树最终的差异,两棵树的差异其实是某些实节点的父亲的不同,这些实节点都连向了某个虚节点,因此改变虚节点父亲就可以改变对应的实节点父亲。
现在考虑差分,从 1 1 1号树开始,我们向编号大的树扫描,考虑它们的差异变化。所有 1 1 1号类型操作 l r x l\;r\;x lrx可以分为两个步骤,第一步是将树 l − 1 l-1 l−1的对应到 l r x l\;r\;x lrx的虚拟节点父亲置为节点 x x x,第二步是将树 r + 1 r+1 r+1的对应到 l r x l\;r\;x lrx的虚拟节点父亲还原。
这些操作其实就是断边和连边操作,可以用 L C T LCT LCT实现。
最后是求距离,考虑求出两点间的 l c a lca lca,对于 u , v u,v u,v而言,我们先 a c c e s s ( u ) access(u) access(u),再 a c c e s s ( v ) access(v) access(v),最后执行 c h [ x ] [ 1 ] = y ch[x][1]=y ch[x][1]=y的 x x x就是对应的 l c a lca lca,那么距离也就好求了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e5+1e4;
int ch[maxn][2],val[maxn],fa[maxn],sz[maxn],par[maxn],rangel[maxn],ranger[maxn],tot=1,ans[maxn];
struct Node{
int op,id,x;
};
struct OP{
int op,a,b,c;
};
struct QRY{
int id,u,v;
};
vector<Node>g[maxn];
vector<QRY>qry[maxn];
void pushup(int u){
sz[u]=sz[ch[u][0]]+sz[ch[u][1]]+val[u];
}
bool is_root(int u){
return ch[fa[u]][0]!=u && ch[fa[u]][1]!=u;
}
int dir(int u){
return ch[fa[u]][1]==u;
}
void connect(int u,int f,int d){
if(u)fa[u]=f;
if(f)ch[f][d]=u;
}
void rotate(int u){
if(is_root(u))return;
int f=fa[u],ff=fa[f],du=dir(u),df=dir(f);
if(is_root(f))fa[u]=ff;
else connect(u,ff,df);
connect(ch[u][du^1],f,du),connect(f,u,du^1);
pushup(f);
}
void splay(int u){
for(int f=fa[u];!is_root(u);f=fa[u]){
if(!is_root(f))(dir(u)^dir(f))?rotate(u):rotate(f);
rotate(u);
}
pushup(u);
}
int access(int u){
int x,y;
for(x=u,y=0;x;x=fa[y=x]){
splay(x),ch[x][1]=y,pushup(x);
}
return y;
}
void link(int f,int u){
par[u]=f;
access(f);
splay(f);
access(u);
splay(u);
fa[u]=f;
}
void cut(int u){
par[u]=0;
access(u);
splay(u);
ch[u][0]=fa[ch[u][0]]=0;
pushup(u);
}
int query(int u,int v){
access(u);
splay(u);
int disu=sz[u]-1;
int lca=access(v);
splay(lca);
int dislca=sz[ch[lca][0]]+val[lca]-1;
splay(v);
int disv=sz[v]-1;
return disu+disv-2*dislca;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
rangel[1]=1,ranger[1]=n;
vector<OP>h;
int cnt=1,ct=0;
while(m--){
int op,l,r,u,v,x;
scanf("%d",&op);
if(!op){
scanf("%d%d",&l,&r);
h.push_back({op,l,r});
++cnt;
rangel[cnt]=l,ranger[cnt]=r;
}else if(op==1){
scanf("%d%d%d",&l,&r,&x);
h.push_back({op,l,r,x});
ct++;
}else{
scanf("%d%d%d",&x,&u,&v);
h.push_back({op,x,u,v});
}
}
for(int i=1;i<=cnt;++i)val[i]=1,pushup(i);
for(int i=1;i<=ct;++i){
if(i==1)link(i,cnt+i);
else link(cnt+i-1,cnt+i);
}
int now=1,ctt=0;ct=0;
for(auto u:h){
if(!u.op){
link(now,++tot);
}else if(u.op==1){
++ct;now=cnt+ct;
u.a=max(u.a,rangel[u.c]);
u.b=min(u.b,ranger[u.c]);
if(u.a>u.b)continue;
g[u.a].push_back({0,cnt+ct,u.c});
if(u.b+1<=n)g[u.b+1].push_back({1,cnt+ct,0});
}else{
qry[u.a].push_back({++ctt,u.b,u.c});
}
}
for(int i=1;i<=n;++i){
for(auto u:g[i]){
if(!u.op){
cut(u.id);
link(u.x,u.id);
}else{
cut(u.id);
if(u.id>cnt+1)link(u.id-1,u.id);
else link(1,u.id);
}
}
for(auto u:qry[i]){
ans[u.id]=query(u.u,u.v);
}
}
for(int i=1;i<=ctt;++i)printf("%d\n",ans[i]);
}