[洛谷P3345][BZOJ4573][ZJOI2016]大森林(LCT)

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 <bits/stdc++.h>

using namespace std;

template <class t>
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 <class t>
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;
};
vector<point>g[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;
}
posted @ 2020-01-15 15:26  花淇淋  阅读(164)  评论(0编辑  收藏  举报