Luogu3348 [ZJOI2016]大森林
Luogu3348 [ZJOI2016]大森林
\(LCT\)
这道题初看十分恐怖,需要维护区间加叶子节点,区间修改生长节点,每棵树中加入叶子节点的位置可能不同,很难对于每棵树直接维护。
虽然每棵树都会产生大量信息,但是我们如果冷静分析一下,就会发现有许多信息是重复的。
我们先使问题简单化。
注意到\(0\)操作中添加的点只能作用在一段区间上,我们可以去除这个限制,让新的节点加到每一棵树上,因为在一次操作中,产生的节点编号是固定的,而且不会在其他操作中生成,同时不可能询问一棵树中不存在的节点,所以我们就可以让节点在每一棵树上生成。
在\(1\)操作中提到,如果一棵树上没有节点\(x\),则不修改,不过我们注意到,存在节点\(x\)的一定是一段区间的树(代码中注意节点\(1\)的存在区间为\([1,n]\)),因此我们将修改区间与存在\(x\)节点的区间取一个交集就可以获得真正修改的区间。
对于操作\(2\),我们观察到每棵树只会添加节点,而没有随意断边连边,这就保证了我们的树在\(t\)时刻的那一部分形态不会在以后被改变,因此我们可以先建完树,然后一次性处理完所有询问,这就启发我们离线下来解决。
假设我们处理完了第\(x\)棵树,然后去处理第\(x+1\)棵树,一个很自然的想法就是利用第\(x\)棵树的信息。
我们可以去寻找第\(x\)棵树和第\(x+1\)棵树的相同点,由于我们去掉了\(0\)操作的区间限制,如果不存在\(1\)操作,第\(x,x+1\)棵树的形态必定相同。
加入了\(1\)操作之后,我们依然可以发现,在两次间隔的\(1\)操作之间,第\(x,x+1\)棵树新添加的节点形态仍然相同,只是生长节点不同罢了,因此我们可以考虑如何把这些节点搬运过去。
我们显然不能一个一个搬运,因此我们可以建立一个虚点来统辖这些节点,这样我们搬运时只需要一步\(cut\),一步\(link\)就可以了。
我们可以每到达一个\(1\)操作,就建立一个虚点,让这些虚点统辖从这次操作到下一次\(1\)操作之间产生的节点。
然后我们记录一下那些位置需要搬运,对于操作\(1\)的真正修改区间\([l,r]\),我们在从\(l-1\)转移到\(l\)时需要搬运一次,从\(r\)转移到\(r+1\)时需要搬运一次。
因此我们总搬运次数不超过\(2M\),可以保证时间复杂度。
我们有了大致的框架,先不修改生长节点,按题目要求建出一棵带虚点的树(可以看成第\(0\)棵树),然后通过搬运依次转移到第\(1\sim n\)棵树,当我们处理完第\(i\)棵树时,解决所有关于第\(i\)棵树的回答。
由于只需要搬运子树,我们的\(LCT\)不需要用到\(makeroot\)操作。
如何处理回答呢?我们先给出结论,假设\(h_i\)表示节点\(i\)到根节点\(1\)的实节点数量,那么一对点\(x,y\)的答案就是\(h_x+h_y-2h_{lca(x,y)}\)。
想必有人会对上面的式子产生疑问,答案难道不是\(x\)到\(y\)路径上实节点数量\(-1\)吗,正常的计算链上节点数量是\(h_x+h_y-h_{lca(x,y)}-h_{fa_{lca(x,y)}}\),因此答案难道不是\(h_x+h_y-h_{lca(x,y)}-h_{fa_{lca(x,y)}}-1\)吗?
解释一下这个问题,首先,如果\(lca(x,y)\)为实节点,那么上式显然成立,因为\(h_{lca(x,y)}=h_{fa_{lca(x,y)}}+1\)。当\(lca(x,y)\)为虚节点时,根据我们建树的过程,虽然\((x,y)\)在树上的\(lca\)是虚点,但是实际上在原树中它们的\(lca\)是虚点的父亲\(u\),因为虚点只是代理管辖一下\(u\)的子孙节点而已,在真正的树中,\(u\)在\(x,y\)所在的链上,而我们在利用\(LCT\)统计实节点数时忽略了这个节点,因此:
那么这道题就完美解决了。
\(Pascal \quad Code:\)
type node=record
fa,num,v:longint;
ch:array[0..1] of longint;
end;
type qry=record
o,x,u,v,idt:longint;
end;
var
n,m,i,opt,rl,cnt,gr,l,r,x,u,v,tot,qc:longint;
zl,zr,re:array[0..200005] of longint;
a:array[0..200005] of node;
g:array[0..400005] of qry;
ans:array[0..200005] of longint;
function cmn(x,y:longint):longint;
begin
if x<y then
exit(x) else
exit(y);
end;
function cmx(x,y:longint):longint;
begin
if x>y then
exit(x) else
exit(y);
end;
function ls(x:longint):longint;
begin
exit(a[x].ch[0]);
end;
function rs(x:longint):longint;
begin
exit(a[x].ch[1]);
end;
function fa(x:longint):longint;
begin
exit(a[x].fa);
end;
function num(x:longint):longint;
begin
exit(a[x].num);
end;
function va(x:longint):longint;
begin
exit(a[x].v);
end;
function isrt(x:longint):boolean;
begin
exit((ls(fa(x))<>x) and (rs(fa(x))<>x));
end;
function id(x:longint):longint;
begin
if ls(fa(x))=x then
exit(0);
exit(1);
end;
procedure update(x:longint);
begin
a[x].num:=num(ls(x))+num(rs(x))+va(x);
end;
procedure connect(x,f,son:longint);
begin
a[x].fa:=f;
a[f].ch[son]:=x;
end;
procedure rot(x:longint);
var
y,r,yson,rson:longint;
begin
y:=fa(x);
r:=fa(y);
yson:=id(x);
rson:=id(y);
if isrt(y) then
a[x].fa:=r else
connect(x,r,rson);
connect(a[x].ch[yson xor 1],y,yson);
connect(y,x,yson xor 1);
update(y);
update(x);
end;
procedure splay(x:longint);
var
y:longint;
begin
while not isrt(x) do
begin
y:=fa(x);
if isrt(y) then
rot(x) else
if id(x)=id(y) then
begin
rot(x);
rot(x);
end else
begin
rot(y);
rot(x);
end;
end;
end;
procedure access(x:longint);
var
y:longint;
begin
y:=0;
while x<>0 do
begin
splay(x);
a[x].ch[1]:=y;
update(x);
y:=x;
x:=fa(x);
end;
end;
function access_lca(x,y:longint):longint;
var
z,ans:longint;
begin
z:=0;
ans:=0;
while x<>0 do
begin
splay(x);
a[x].ch[1]:=z;
update(x);
z:=x;
x:=fa(x);
end;
inc(ans,num(z));
z:=0;
while y<>0 do
begin
splay(y);
a[y].ch[1]:=z;
update(y);
z:=y;
y:=fa(y);
end;
y:=z;
inc(ans,num(z));
ans:=ans-(num(z)-num(rs(z)))*2;
exit(ans);
end;
procedure link(x,y:longint);
begin
a[x].fa:=y;
end;
procedure cut(x:longint);
begin
access(x);
splay(x);
a[ls(x)].fa:=0;
a[x].ch[0]:=0;
update(x);
end;
procedure ins(o,x,u,v,idt:longint);
begin
inc(tot);
g[tot].o:=o;
g[tot].x:=x;
g[tot].u:=u;
g[tot].v:=v;
g[tot].idt:=idt;
end;
function cmp(x,y:qry):boolean;
begin
if x.x<>y.x then
exit(x.x<y.x);
exit(x.o<y.o);
end;
procedure sort(l,r:longint);
var
i,j,k:longint;
t:qry;
begin
i:=l;
j:=r;
k:=(l+r) div 2;
t:=g[k];
g[k]:=g[i];
while i<j do
begin
while (i<j) and cmp(t,g[j]) do
dec(j);
if i<j then
begin
g[i]:=g[j];
inc(i);
end;
while (i<j) and cmp(g[i],t) do
inc(i);
if i<j then
begin
g[j]:=g[i];
dec(j);
end;
end;
g[i]:=t;
if i-1>l then
sort(l,i-1);
if i+1<r then
sort(i+1,r);
end;
begin
read(n);
read(m);
cnt:=2;
gr:=2;
rl:=1;
qc:=0;
re[1]:=1;
a[1].v:=1;
zl[1]:=1;
zr[1]:=n;
update(1);
link(2,1);
for i:=1 to m do
begin
read(opt);
case opt of
0: begin
read(l);
read(r);
inc(rl);
inc(cnt);
a[cnt].v:=1;
update(cnt);
re[rl]:=cnt;
link(cnt,gr);
zl[rl]:=l;
zr[rl]:=r;
end;
1: begin
read(l);
read(r);
read(x);
l:=cmx(l,zl[x]);
r:=cmn(r,zr[x]);
if l>r then
continue;
x:=re[x];
inc(cnt);
link(cnt,gr);
ins(0,l,cnt,x,0);
ins(0,r+1,cnt,gr,0);
gr:=cnt;
end;
2: begin
read(x);
read(u);
read(v);
inc(qc);
ins(1,x,re[u],re[v],qc);
end;
end;
end;
sort(1,tot);
for i:=1 to tot do
begin
if g[i].o=0 then
begin
cut(g[i].u);
link(g[i].u,g[i].v);
end else
ans[g[i].idt]:=access_lca(g[i].u,g[i].v);
end;
for i:=1 to qc do
writeln(ans[i]);
end.