P3348 [ZJOI2016]大森林 LCT
题意:
分析:
巨佬1 说这是水题,应该一眼切,我还是太菜了/kk
不讲思考过程了,因为我压根就不会,思考了寂寞
具体做法就是,我们发现询问可以离线,那么对操作进行排序,利用LCT的动态维护的功能就可以在一棵树上通过删改得到所有情况的答案
我们开始分操作考虑,首先我们发现一个不是很显然的性质:所有询问的点对一定通过实际存在的点相连(按巨佬的话说,就是在一个“连通块里”),也就是说查询路径上的点一定是实际存在的
由此我们可以发现 (0) 操作,可以看作全局生长,因为你在其他生长节点上长上去的点不是实际存在的节点,不会影响别的询问
接着考虑 (1) 操作,我们将该生长节点影响的区间((l,r))拿出来,然后我们发现对于一个 (1 l r x) 的操作,当扫到第 (l) 棵树的时候,需要把 (l-1) 这棵树上生长节点所连的同时属于 (l) 这棵树的节点 连到 (x) 上,也就是说我们需要让 (LCT) 支持修改子树父亲这一操作,其实可以直接 (ETT), 但是我们可以通过一个小 (trick) 实现,就是设立虚点,将需要修改的节点连在一个没有任何信息的虚点上,然后直接对虚点进行修改相当于同时修改了它的子树
然后考虑 (2) 操作,相当于用 (lct) 查询任意两点的 (lca) ,具体实现就是先 (access(x)) 然后 (access(y)) 的返回值就是(lca) ,然后我们还要查询树上两点间的距离,其实我们可以直接用 (siz) 就能表示出 (dep) ,我们 (access(x) o splay(x)) 后 (x) 的 (siz) 就是它内部节点数
tip:
-
对于 (1) 操作的边界,一定要用 (x) 存在的左右边界和询问的左右边界进行取min,max ,这个边界一定要求对,因为我们把 (0) 操作全局化了,所以必须保证询问合法,不然会 (WA)
-
可以把 (0,1) 两个操作看成同一种操作,(0) 操作相当于连一个虚点然后虚点连接了一个点权为 (1) 的实点, (1) 操作相当于连一个虚点然后虚点连一个点权为 (0) 的虚点当做生长节点,由于点权为 (0) 所以在距离上来说,这个新的生长节点和原来的生长节点是同一个,不会对答案造成影响
-
因为最开始所有的树的生长节点都是 (1) 所以我们可以看成最开始有一个操作 (1 1 n 1) ,所以我们最开始要新建一个节点 (1) 作为生长节点,建一个节点 (2) 作为 (1) 所连的虚点
-
由于实点的父亲不会变,一定是在他之前离他最近的那个生长节点,所以我们可以一开始就把所有的 (0) 操作处理完,建出所有的实点
-
由于这个题默认以 (1) 为树根当做起始生长节点,所以树边是有向的,不能进行 (makeroot,pushdown,split) 等会改变树形态的操作
代码:
#include<bits/stdc++.h>
using namespace std;
namespace zzc
{
int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 1e6+5;
int n,m,idx,qt,tot,lst;
int lef[maxn],rig[maxn],id[maxn],ans[maxn];
namespace lct
{
int cnt,top;
int qu[maxn],val[maxn],sum[maxn],ch[maxn][2],fa[maxn],rev[maxn];
void new_node(int x){cnt++;val[cnt]=sum[cnt]=x;}
bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
void pushup(int x){sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];}
void pushdown(int x){int l=ch[x][0],r=ch[x][1];if(rev[x]){swap(ch[x][0],ch[x][1]),rev[l]^=1,rev[r]^=1,rev[x]=0;}}
void rotate(int x)
{
int y=fa[x],z=fa[y],l,r;
if(ch[y][0]==x)l=0;else l=1;r=l^1;
if(!isroot(y)){if(ch[z][0]==y)ch[z][0]=x;else ch[z][1]=x;}
fa[x]=z;fa[y]=x;fa[ch[x][r]]=y;
ch[y][l]=ch[x][r];ch[x][r]=y;
pushup(x);pushup(y);
}
void splay(int x)
{
qu[top=1]=x;
for(int i=x;!isroot(i);i=fa[i])qu[++top]=fa[i];
for(int i=top;i;i--) pushdown(qu[i]);
while(!isroot(x))
{
int y=fa[x],z=fa[y];
if(!isroot(y))
{
if((ch[z][0]==y)^(ch[y][0]==x))rotate(x);
else rotate(y);
}
else rotate(x);
}
pushup(x);
}
int access(int x){int t=0;for(;x;t=x,x=fa[x]){splay(x);ch[x][1]=t;pushup(x);}return t;}
void makeroot(int x){access(x);splay(x);rev[x]^=1;}
void link(int x,int y){splay(x);fa[x]=y;}
void cut(int x){access(x);splay(x);fa[ch[x][0]]=0,ch[x][0]=0,pushup(x);}
int query(int x,int y)
{
int res=0,lca;
access(x);splay(x);res+=sum[x];
lca=access(y);splay(y);res+=sum[y];
access(lca);splay(lca);res-=2*sum[lca];
return res;
}
}
using namespace lct;
struct que
{
int pos,opt,x,y;
que(){}
que(int pos,int opt,int x,int y):pos(pos),opt(opt),x(x),y(y){}
bool operator <(const que &b)const
{
return pos==b.pos?opt<b.opt:pos<b.pos;
}
}q[maxn];
void work()
{
int opt,x,y,z;
n=read();m=read();
new_node(1);lef[1]=1;rig[1]=n;id[idx=1]=1;
new_node(0);link(2,1);lst=2;
for(int i=1;i<=m;i++)
{
opt=read();
if(opt==0)
{
x=read();y=read();
new_node(1);lef[++idx]=x;rig[idx]=y;id[idx]=cnt;
q[++tot]=que(1,i-m,cnt,lst);
}
else if(opt==1)
{
x=read();y=read();z=read();
x=max(x,lef[z]);y=min(y,rig[z]);
if(x<=y)
{
new_node(0);link(cnt,lst);
q[++tot]=que(x,i-m,cnt,id[z]);
q[++tot]=que(y+1,i-m,cnt,lst);
lst=cnt;
}
}
else if(opt==2)
{
x=read();y=read();z=read();
q[++tot]=que(x,++qt,id[y],id[z]);
}
}
sort(q+1,q+tot+1);
for(int i=1,j=1;i<=n;i++)
{
for(;i==q[j].pos;j++)
{
if(q[j].opt<0) cut(q[j].x),link(q[j].x,q[j].y);
else ans[q[j].opt]=query(q[j].x,q[j].y);
}
}
for(int i=1;i<=qt;i++) printf("%d
",ans[i]);
}
}
int main()
{
zzc::work();
return 0;
}