那绚丽的云之彼岸,对我来说仍旧是遥远的国度。

题目大意

例题链接

给定一棵树,每次可以:

  • 修改该树的根节点 \(r\)

  • 将结点 \(x\) 到结点 \(y\) 的路径中所有结点的权值都修改成 \(v\)

  • 查询树 \(T\) 以当前根结点 \(r\) 为根时,结点 \(x\) 的子树内最小的点权

结点总数 \(n \leq 10^5\),操作总数 \(m \leq 10^5\),$0 < $ 最大点权 \(w< 2^{31}\)

解题思路

如果忽略题目中给出的 换根 操作,这道题就是一道普通的树链剖分模板,直接用线段树维护区间最小值即可。因此,我们主要的思考方向就是如何处理换根操作。

最暴力的思路是:每换一次根,都做一次树链剖分预处理。显然这样的方法是行不通的,所以我们需要只预处理一次,就可以求出以所有结点为根的情况。

我们不妨以结点 \(1\) 为根来进行预处理。接下来,对于每一次询问的结点 \(x\),我们进行 分类讨论

  1. \(x\)原树中 是当前根结点 \(r\) 的子结点,这样实质上对 \(x\) 及其子树没有任何影响,直接查询即可

  2. \(x\)原树中 的深度与当前根结点 \(r\) 相等,同样对 \(x\) 及其子树没有任何影响,直接查询即可

  3. \(x\)原树中 的深度比当前根结点小,但是不是 \(x\) 的祖先结点,即不在结点 \(1\) 到结点 \(r\) 的路径,同上

  4. \(x\)原树中 的深度比当前结点小,且 \(x\) 是当前根结点 \(r\) 的祖先结点,下文另行讨论。

显然,前三种情况都很好处理,真正需要特判的是第四种情况。在第四种情况中,原树上的当前根结点 \(r\) 是查询结点 \(x\) 的后代,但是在新树中却是 \(x\) 的祖先。假如直接查询 \(x\) 的子树,会将新树中 \(x\) 的祖先也算进 \(x\) 的子树。

【题解】P3919 - 遥远的国度_结点

如上图,左边为以待查询结点 \(1\) 为根结点的原树,右边为以结点 \(5\) 为根结点的新树。所有原本在结点 \(1\) 到结点 \(5\) 路径上的结点,除了结点 \(1\),其他都不在结点 \(1\) 的子树内,而是成为了结点 \(1\) 的祖先

我们可以发现,结点 \(3\) 是一个特殊的结点。原树中结点 \(3\) 的子树全都不是新树中结点 \(1\) 的子树,原树中不是结点 \(3\) 的后代的结点都是新树中结点 \(1\) 的子树。

因为结点 \(3\) 的子结点 \(5\) 是新树的根结点,是新树中结点 \(1\) 的祖先;其他结点都与结点 \(1\) 一样,和新树中深度为 \(2\)的结点 \(3\) 相连,也不在新树中结点 \(1\) 的子树内,所以结点 \(3\) 的后代节点都和结点 \(1\) 不相交,也就是不在新树中结点 \(1\) 的子树内。

还可以发现,结点 \(3\) 刚好是 待查询结点 \(x\) 到当前根结点 \(r\) 的路径中,结点 \(x\) 的子结点。 由此,我们可以得出结论:新树中待查询结点 \(x\) 的子树,就是原树中除去 \(x\)\(r\) 的路径中,\(x\) 的子结点的子树的部分。具体到上图,就是原树中除去结点 \(3\) 的子树的部分。

根据这个性质,我们就可以解决换根的操作,进而解决这道题目。查询祖孙关系的时候可以使用树剖实现的 \(LCA\),假如原树中的 \(LCA(x, r) = x\),说明 \(x\) 在原树中是 \(r\) 的祖先,反之不是。

参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long

const int maxn = 100005; 
const int maxm = 200005;
const int inf = 1e16 + 5;

struct Edge
{
	int to, nxt;
} edge[maxm];

struct node
{
	int l, r, val, lazy;
} tree[5 * maxn];

int n, q, root, cnt;
int head[maxn], pos[maxn], rk[maxn], son[maxn];
int w[maxn], size[maxn], dep[maxn], top[maxn], fa[maxn];

void add_edge(int u, int v)
{
	cnt++;
	edge[cnt].to = v;
	edge[cnt].nxt = head[u];
	head[u] = cnt;
}

void dfs1(int u, int f)
{
	fa[u] = f;
	dep[u] = dep[f] + 1;
	size[u] = 1;
	for (int i = head[u]; i; i = edge[i].nxt)
	{
		if (edge[i].to != f)
		{
			dfs1(edge[i].to, u);
			size[u] += size[edge[i].to];
			if (size[edge[i].to] > size[son[u]])
				son[u] = edge[i].to;
		}
	}
}

void dfs2(int u, int t)
{
	cnt++;
	pos[u] = cnt;
	rk[cnt] = u;
	top[u] = t;
	if (son[u])
		dfs2(son[u], t);
	for (int i = head[u]; i; i = edge[i].nxt)
		if (edge[i].to != fa[u] && edge[i].to != son[u])
			dfs2(edge[i].to, edge[i].to);
}

void push_up(int k)
{
	tree[k].val = min(tree[2 * k].val, tree[2 * k + 1].val);
}

void push_down(int k)
{
	if (tree[k].l == tree[k].r)
	{
		tree[k].lazy = 0;
		return;
	}
	if (!tree[k].lazy)
		return;
	tree[2 * k].val = tree[k].lazy;
	tree[2 * k + 1].val = tree[k].lazy;
	tree[2 * k].lazy = tree[k].lazy;
	tree[2 * k + 1].lazy = tree[k].lazy;
	tree[k].lazy = 0;
}

void build(int k, int l, int r)
{
	tree[k].l = l;
	tree[k].r = r;
	if (l == r)
	{
		tree[k].val = w[rk[l]];
		return;
	}
	int mid = (l + r) / 2;
	build(2 * k, l, mid);
	build(2 * k + 1, mid + 1, r);
	push_up(k);
}

void update(int k, int l, int r, int x)
{
	if (tree[k].l >= l && tree[k].r <= r)
	{
		tree[k].val = x;
		tree[k].lazy = x;
		return;
	}
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2;
	if (l <= mid)
		update(2 * k, l, r, x);
	if (r > mid)
		update(2 * k + 1, l, r, x);
	push_up(k);
}

int query(int k, int l, int r)
{
	if (tree[k].l >= l && tree[k].r <= r)
		return tree[k].val;
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2, val = inf;
	if (l <= mid)
		val = min(val, query(2 * k, l, r));
	if (r > mid)
		val = min(val, query(2 * k + 1, l, r));
	return val;
}

void add_path(int x, int y, int z)
{
	while (top[x] != top[y])
	{
		if (dep[top[x]] <= dep[top[y]])
			swap(x, y);
		update(1, pos[top[x]], pos[x], z);
		x = fa[top[x]];
	}
	if (dep[x] >= dep[y])
		swap(x, y);
	update(1, pos[x], pos[y], z);
}

int get_son(int x, int y)
{
	while (top[x] != top[y])
	{
		if (dep[top[x]] <= dep[top[y]])
			swap(x, y);
		if (fa[top[x]] == y)
			return top[x];
		x = fa[top[x]];
	}
	if (dep[x] >= dep[y])
		swap(x, y);
	return son[x];
}

int query_tree(int u)
{
	if (u == root)
		return query(1, 1, n);
	if (dep[u] >= dep[root])
		return query(1, pos[u], pos[u] + size[u] - 1);
	int v = get_son(u, root);
	if (fa[v] != u)
		return query(1, pos[u], pos[u] + size[u] - 1);
	return min(query(1, 1, pos[v] - 1), query(1, pos[v] + size[v], n));
}

signed main()
{
	int opt, u, v, x;
	scanf("%lld%lld", &n, &q);
	for (int i = 1; i <= n - 1; i++)
	{
		scanf("%lld%lld", &u, &v);
		add_edge(u, v);
		add_edge(v, u);
	}
	for (int i = 1; i <= n; i++)
		scanf("%lld", &w[i]);
	scanf("%lld", &root);
	cnt = 0;
	dfs1(1, 0);
	dfs2(1, 0);
	build(1, 1, n);
	for (int i = 1; i <= q; i++)
	{
		scanf("%lld", &opt);
		if (opt == 1)
			scanf("%lld", &root);
		else if (opt == 2)
		{
			scanf("%lld%lld%lld", &u, &v, &x);
			add_path(u, v, x);
		}
		else
		{
			scanf("%lld", &u);
			printf("%lld\n", query_tree(u));
		}
	}
	return 0;
}