Solution
- 首先发现把 \(2\) 操作都丢到最后处理不会影响答案
 
- 那么可以把所有修改操作拆成在 \(l\) 处加入,在 \(r+1\) 处删除
 
- 把所有操作读入之后按树的编号顺序处理
 
- 动态维护一棵树,即处理第 \(i\) 棵树后,这棵树就是第 \(i\) 棵树的结构
 
- 但是不维护原树结构,而是维护一个等价的东西:
 
- 给每个 \(1\) 操作建一个虚点,每个 \(0\) 操作(也可以说是原树上的每个点)建一个实点
 
- 实点的父亲始终不变(所以一开始就连好),都是在它之前的第一个 \(1\) 操作的虚点
 
- 处理第 \(i\) 棵树后,如果虚点 \(x\) 对应的操作在这棵树上生效,那么 \(x\) 的父亲为它对应的实点,否则 \(x\) 向上一个虚点连边
 
- 一开始可以看作执行一个把所有树的生长节点都改为 \(1\) 的操作,因此一开始多建一个虚点,它的父亲是实点 \(1\)
 
- 然后一开始还要把所有虚点都连向上一个
 
- 构建多余的实点不会影响答案,但 \(1\) 操作 (\(1\) \(l\) \(r\) \(x\)) 的区间 \([l,r]\) 里不一定所有树都长过编号为 \(x\) 的节点,但是长过编号为 \(x\) 的节点的树一定构成一个区间 \([u,v]\),所以 \([l,r]\) 和 \([u,v]\) 取并集即可
 
- 操作 \(0\) 的影响也只有这些,不用拆成 \(l\) 和 \(r+1\)
 
- 操作 \(1\) 在 \(l\) 处加入:把对应虚点连向对应实点
 
- 在 \(r+1\) 处删除:把对应虚点连回上一个虚点
 
- 对于第 \(i\) 棵树,处理好所有关于它的修改之后即可查询答案
 
- 查询答案:记 \(s(x)\) 为 \(x\) 到根路径上点权之和,显然实点权值为 \(1\),虚点为 \(0\)
 
- 那么 \(ans=s(x)+s(y)-s(lca(x,y))\)
 
- 注意此题中不能 \(makeroot\),因为不能打乱父子关系,树边是有向的
 
- 找 \(lca\):先 \(access(x)\),然后 \(access(y)\)
 
- \(access(y)\) 的循环结束条件:已经到了和根同一条实链上,然后不是有个 \(rc[u]=v\) 这样的东西吗,此时的 \(u\) 即 \(lca(x,y)\)
 
- 好像不太好描述清楚,那么上代码:
 
inline int access(int x)
{
   int y;
   for (y = 0; x; y = x, x = fa[x])
   {
   	splay(x);
   	rc[x] = y;
   	if (y) fa[y] = x;
   	upt(x);
   }
   return y;
}
- 可以维护每个点 \(splay\) 上的子树点权和,查询 \(s(x)\) 只要 \(splay(x)\) 然后用自己点权加上左子树点权之和
 
Code
#include 
using namespace std;
template 
inline void read(t & res)
{
	char ch;
	while (ch = getchar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
	res  = res * 10 + (ch ^ 48);
}
template 
inline void print(t x)
{
	if (x > 9) print(x / 10);
	putchar(x % 10 + 48);
}
const int e = 1e6 + 5;
struct point
{
	int opt, x, y, id;
};
vectorg[e];
int lc[e], rc[e], fa[e], num, sum[e], n, m, c1 = 1, c2 = 1, q, ans[e], val[e], lst[e];
int ld[e], rd[e];
inline bool which(int x)
{
	return rc[fa[x]] == x;
}
inline bool isroot(int x)
{
	return !fa[x] || (lc[fa[x]] != x && rc[fa[x]] != x);
}
inline void upt(int x)
{
	sum[x] = val[x];
	if (lc[x]) sum[x] += sum[lc[x]];
	if (rc[x]) sum[x] += sum[rc[x]];
}
inline void rotate(int x)
{
	int y = fa[x], z = fa[y], b;
	if (z && !isroot(y)) (lc[z] == y ? lc[z] : rc[z]) = x;
	b = (lc[y] == x ? rc[x] : lc[x]);
	fa[x] = z;
	fa[y] = x;
	if (b) fa[b] = y;
	if (lc[y] == x)
	{
		rc[x] = y;
		lc[y] = b;
	}
	else
	{
		lc[x] = y;
		rc[y] = b;
	}
	upt(y);
	upt(x);
}
inline void splay(int x)
{
	while (!isroot(x))
	{
		if (!isroot(fa[x]))
		{
			if (which(x) == which(fa[x])) rotate(fa[x]);
			else rotate(x);
		}
		rotate(x);
	}
}
inline int access(int x)
{
	int y;
	for (y = 0; x; y = x, x = fa[x])
	{
		splay(x);
		rc[x] = y;
		if (y) fa[y] = x;
		upt(x);
	}
	return y;
}
inline void link(int x, int y)
{
	lst[x] = y;
	splay(x);
	fa[x] = y;
}
inline void cut(int x)
{
	access(x);
	splay(x);
	fa[lc[x]] = 0;
	lc[x] = 0;
	upt(x);
}
inline int lca(int x, int y)
{
	access(x);
	return access(y);
}
inline int query(int x)
{
	access(x);
	splay(x);
	return val[x] + sum[lc[x]];
}
int main()
{
	int i, opt, l, r, x, j;
	read(n); read(m);
	link(c1 + m, c2);
	val[c2] = sum[c2] = 1;
	ld[1] = 1;
	rd[1] = n;
	for (i = 1; i <= m; i++)
	{
		read(opt);
		read(l);
		read(r);
		if (opt == 0) 
		{
			c2++;
			ld[c2] = l;
			rd[c2] = r;
			val[c2] = sum[c2] = 1;
			cut(c2);
			link(c2, c1 + m);
		}
		else if (opt == 1)
		{
			c1++;
			cut(c1 + m);
			link(c1 + m, c1 + m - 1);
			read(x);
			l = max(l, ld[x]);
			r = min(r, rd[x]);
			if (l > r) continue;
			g[l].push_back((point){1, c1 + m, x, 0});
			g[r + 1].push_back((point){-1, c1 + m, c1 + m - 1, 0});
		}
		else
		{
			read(x);
			g[l].push_back((point){2, r, x, ++q});
		}
	}
	point u;
	for (i = 1; i <= n; i++)
	{
		for (auto u : g[i])
		if (u.opt == -1 || u.opt == 1) 
		{
			cut(u.x);
			link(u.x, u.y);
		}
		for (auto u : g[i])
		if (u.opt == 2) ans[u.id] = query(u.x) + query(u.y) - 2 * query(lca(u.x, u.y));	
	}
	for (i = 1; i <= q; i++)
	{
		print(ans[i]);
		putchar('\n');
	}
	return 0;
}