zl程序教程

您现在的位置是:首页 >  其它

当前栏目

BZOJ4573[ZJOI2016] 大森林

森林
2023-09-27 14:28:32 时间

原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4573

大森林

Description

小Y家里有一个大森林,里面有n棵树,编号从1到n。一开始这些树都只是树苗,只有一个节点,标号为1。这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力。小Y掌握了一种魔法,能让第l棵树到第r棵树的生长节点长出一个子节点。同时她还能修改第l棵树到第r棵树的生长节点。她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问呢?

Input

第一行包含 2 个正整数 n,m,共有 n 棵树和 m 个操作。接下来 m 行,每行包含若干非负整数表示一个操作,操作格式为:

0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例如,第一个 0 号操作产生的子节点标号为 2), l 到 r 之间的树长出的节点标号都相同。保证 1≤l≤r≤n 。

1 l r x 表示将第 l 棵树到第 r 棵树的生长节点改到标号为 x 的节点。对于 i (l≤i≤r)这棵树,如果标号 x的点不在其中,那么这个操作对该树不产生影响。保证 1≤l≤r≤n , x 不超过当前所有树中节点最大的标号。

2 x u v 询问第 x 棵树中节点 u 到节点 v 点的距离,也就是在第 x 棵树中从节点 u 和节点 v 的最短路上边的数量。

保证1≤x≤n,这棵树中节点 u 和节点 v 存在。N<=105,M<=2*105

Output

输出包括若干行,按顺序对于每个小Y的询问输出答案

Sample Input

5 5
0 1 5
1 2 4 2
0 1 4
2 1 1 3
2 2 1 3

Sample Output

1
2

题解

做法当然是大力LCT然后乖乖TLE

首先,很显然的是,询问中每棵树都是独立的,互不干扰,再加上题目中同一次 0 0 0操作生成的节点编号是一样的,一起操作非常麻烦,我们不如一棵树一棵树的处理操作。因此,我们考虑将操作离线下来,从左向右处理对于每棵树的询问。

那么如何处理每个操作呢?

那么我们到底离线了些啥,怎么离线??

观察所有的操作,如果没有 1 1 1操作,那么所有点都长到一起,随便乱接都无所谓。类比一下,没有改变生长点的时候,由于是长到同一个节点上,而我们的询问是针对两点间路径的,所以一棵树上即使长了不属于自己的节点也不会影响答案。所以针对每次更改生长点的操作,我们建立一个虚节点;生长节点时,建立一个实节点,连接到最近生成的虚节点上,我们将所有虚节点按时间顺序串起来。

考虑我们处理操作的过程:先把所有节点长好,再处理生长节点的修改和询问。

那么 1 1 1操作到底改了什么呢?对于一个形如 1   l e   r i   x 1\ le\ ri\ x 1 le ri x的操作,我们新建了一个虚点。虚点的有效区间为 l e → r i le\to ri leri,当我们处理到 l e le le时,把对应的子树切下来,接到对应的实节点上,统计答案;到 r i + 1 ri+1 ri+1时再把那个子树切下来接回原来的虚点上。

对于子树嫁接这个操作 L C T \mathcal{LCT} LCT直接搞有点麻烦(会 E T T ETT ETT的大佬请坐下),为此,我们引入一些“虚点”,把节点都连到虚点上,嫁接时直接把虚点连过去。因为要统计路径长度,我们把虚点的权值设为 0 0 0,实点权值为 1 1 1,维护一下子树大小即可。

值得一提的是,当统计路径长度的时候我们不能直接 s p l i t ( x , y ) \mathcal{split(x,y)} split(x,y),因为原来的树为有根树,而 m a k e r o o t \mathcal{makeroot} makeroot的时候因为有区间反转之类的操作,会破坏原来的父子关系;另外,两个节点的 L C A \mathcal{LCA} LCA有可能是虚点,它们真正的 L C A \mathcal{LCA} LCA是该虚点的某个祖先,所以是不行的。但是我们可以借用树上差分的思路,借助 L C A \mathcal{LCA} LCA来解决这个问题:
a n s = s i z [ x ] + s i z [ y ] − 2 × s i z [ L C A x , y ] ans=siz[x]+siz[y]-2\times siz[LCA_{x,y}] ans=siz[x]+siz[y]2×siz[LCAx,y]

那么如何求 L C T \mathcal{LCT} LCT上的 L C A \mathcal{LCA} LCA呢?

我们先 a c c e s s ( x ) , s p l a y ( x ) \mathcal{access(x)},splay(x) access(x),splay(x),再 a c c e s s ( y ) access(y) access(y),在 a c c e s s ( y ) access(y) access(y)的过程中最后跳到的虚边对应节点就是 x , y x,y x,y L C A \mathcal{LCA} LCA,简单的图示:

access(x):

splay(x):

access(y):


值得注意的是,上述性质也是得益于没有换根才有的。

代码
#include<bits/stdc++.h>
#define ls son[v][0]
#define rs son[v][1]
using namespace std;
const int M=1e6+5;
struct sd{int pos,op,x,y;};
bool operator <(sd a,sd b){return a.pos==b.pos?a.op<b.op:a.pos<b.pos;}
int dad[M],son[M][2],siz[M],ans[M],tot,last,id[M],idd,top,le[M],ri[M],qs,n,m;
bool r[M];sd que[M];
bool notroot(int v){return son[dad[v]][0]==v||son[dad[v]][1]==v;}
void up(int v){siz[v]=siz[ls]+siz[rs]+r[v];}
void spin(int v)
{
	int f=dad[v],ff=dad[f],k=son[f][1]==v,w=son[v][!k];
	if(notroot(f))son[ff][son[ff][1]==f]=v;son[v][!k]=f;son[f][k]=w;
	if(w)dad[w]=f;dad[f]=v;dad[v]=ff;up(f);up(v);
}
void splay(int v)
{
	int f,ff;
	while(notroot(v))
	{
		f=dad[v];ff=dad[f];
		if(notroot(f))spin((son[f][0]==v)^(son[ff][0]==f)?v:f);
		spin(v);
	}
}
int access(int v){int f=0;for(;v;v=dad[f=v])splay(v),rs=f,up(v);return f;}
void link(int x,int y){splay(x);dad[x]=y;}
void cut(int v){access(v);splay(v);dad[ls]=0;ls=0;up(v);}
int query(int x,int y)
{
	int ans=0,t;
	access(x);splay(x);ans+=siz[x];
	t=access(y);splay(y);ans+=siz[y];
	access(t);splay(t);ans-=2*siz[t];
	return ans;
}
void _new(int x){r[++tot]=x;siz[tot]=x;}
void in()
{
	scanf("%d%d",&n,&m);
	_new(1);idd=1;le[1]=1;ri[1]=n;id[1]=1;
	_new(0);last=2;link(2,1);
	int op,a,b,c,l,r;
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d%d",&op,&a,&b);
		switch(op)
		{
			case 0:_new(1);le[++idd]=a;ri[idd]=b;id[idd]=tot;que[++top]=(sd){1,i-m,tot,last};break;
			case 1:
			{
				scanf("%d",&c);a=max(a,le[c]);b=min(b,ri[c]);
				if(a<=b)
				{
					_new(0);link(tot,last);
					que[++top]=(sd){a,i-m,tot,id[c]};
					que[++top]=(sd){b+1,i-m,tot,last};
					last=tot;
				}
				break;
			}
			case 2:scanf("%d",&c);que[++top]=(sd){a,++qs,id[b],id[c]};
		}
	}
}
void ac()
{
	sort(que+1,que+1+top);
	for(int i=1,j=1;i<=n;++i)
	{
		for(;j<=top&&que[j].pos==i;++j)
		if(que[j].op<=0)cut(que[j].x),link(que[j].x,que[j].y);
		else ans[que[j].op]=query(que[j].x,que[j].y);
	}
	for(int i=1;i<=qs;++i)
	printf("%d\n",ans[i]);
}
int main()
{
	in();ac();
	return 0;
}