CF1017G – The Tree – 题解与思路重现

作者:

在

CodeForces Round #502 Problem G – The Tree

这是lty数据结构专题里唯二自己做出来的题目中的一道。

如果不包含“将 $x$ 的子树全部染白”的操作,应当怎样处理?

题目所给操作可以这样转述:令 $x$ 为本次染色操作的节点。若 $x$ 为白色,则立刻染黑并退出;否则我们找到其子树中的,与 $x$ 相连的黑色连通块,则对于每个连通块底部节点,若其不是叶子节点,我们将其儿子染黑。

由于本题的树没有特殊性质,且将来加入回退操作,故而难以维护整个连通块以及其叶子节点。因此,我们可以从“在何种情况下,节点 $x$ 被染黑”的方向考虑。

显然,只有从在 $x$ 到根的路径上的节点开始的染色操作,才可能波及到 $x$。假如最终 $x$ 被染黑的一瞬,它被包含在了以 $\text{comprt}$ 为根的连通块中。令链 $\langle\text{comprt}, x\rangle$ 为 $\left\{x_1,x_2,\dots,x_n \right\},x_1=\text{comprt},x_n=x$,那么自然想到,一种一定合法的染色序列是 $\{y_1,y_2,\cdots,y_n\}$,其中有 $\forall k \in [1, n] \cap \mathbb{Z},y_k \in \{x_1,\cdots,x_k\}$,也即我们每一步都在该连通块内部,并将连通块的最大深度推进一层。

这种序列确实合法(它是充分条件),但这真的是 $x$ 被染黑的必要条件么?

我们发现,不论在第 $k$ 轮染色的节点为 $y_k$ 是否包含在以 $\text{comprt}$ 为根的“目标连通块”中,它总是能够将 $\langle \text{comprt}, x\rangle$ 被染黑的节点层数推进一层。因为本题的操作保证递归的染色一定不会在黑色节点终止,故而总有一层白色节点变黑。就算在某次染色后,上下两个连通块发生合并,则下次染色在合并后的大连通块任意位置进行,都将涉及到下层连通块的儿子节点。那么,当 $\langle \text{comprt}, x\rangle$ 所有层都被染黑时,$x_n=x$ 自然也无路可逃了。正因如此,我们实际上根本不关心每一次染色的位置。则我们立刻得出以下结论:

令 $\langle \text{rt}, x\rangle=\{x_1=\text{rt},x_2,\cdots,x_n=x\}$ 是 $x$ 到根的祖先后代链。令 $\operatorname{pre}_x(k)=\sum_{i=1}^{k}[x_i \text{is colored black}]$(染黑节点数的前缀和),则 $x$ 被染黑的充分必要条件是 $\exists k \in [0,n) \cap \mathbb{Z}, \operatorname{pre}_x(n)-\operatorname{pre}_x(k)\geq n-k$。

该式是一个树上根到某节点路径的前缀和统计。我们不妨转写其为 $\operatorname{pre}_x(n)-n\geq \operatorname{pre}_x(k)-k$,使其两侧均只分别与 $n, k$ 相关,易于查询和维护。故而我们通过线段树结合重链剖分维护(对于每个节点 $y$ 而言)$\operatorname{pre}_y(\operatorname{dep}(y))-\operatorname{dep}(y)$,在染色时对子树所有节点加上 $1$,查询 $x$ 是否为黑色时考虑到根的路径上该式的最小值,将之与 $\operatorname{pre}_x(\operatorname{dep}(x))-\operatorname{dep}(x)$ 比较即可。

现在加入子树清空操作。假如我们现将 $x$ 及其子树全部赋为白色,则

  • 子树内部进行的所有操作失效;
  • 在祖先后代链 $\langle \text{rt},x\rangle$ 进行的部分操作失效。

也即,根据上文提到的操作序列的性质,我们确有可能在 $\langle \text{rt},x\rangle$ 上染色,使得连通块扩展到 $x$ 的子树内。但现在该连通块最多只能够扩展到 $\operatorname{fa}(x)$。如果仍为扩展到 $x$,则以下的调整均可以忽略;否则,是否要一一按顺序找出这些操作,然后尽数删除它们造成的贡献么?显然不行,其它子树内部的由这些操作产生的贡献仍然需要保留。故而我们尝试以等价方式调整 $x$ 及其子树的前缀和,使其满足 $\forall k \in [0, \operatorname{dep}(x)), \operatorname{pre}_x(\operatorname{dep}(x))-\operatorname{dep}(x)<\operatorname{pre}_x(k)-k$。

那我们直接把 $\operatorname{pre}_x(\operatorname{dep}(x))$ 调整为 $\min\{\operatorname{pre}_x(k)-k\mid k \in (0, \operatorname{dep}(x)\}-1$ 就行了?

事实证明,这样做是正确的。考虑该调整是否与之前的条件契合:$x$ 子树以外的数据没有受到改变,若其先前自洽,则调整后仍自洽;对于 $x$ 子树内节点,由于内部操作被清空,必然有 $\forall y \in \text{subtree rooted at }x, \operatorname{pre}_y(\operatorname{dep}(y))=\operatorname{pre}_x(\operatorname{dep}(x))$,而在计算 $x$ 是否染黑时,只利用该式子的相对大小关系,而非利用不等号左右两侧式子之差作精确统计,同时 $x$ 祖先们的染色操作造成的联通块层数扩张仍将如实反映到各个子树中;对于将来在子树内部的操作,其相对大小关系显然仍是自洽的。故而该调整的正确性得到保障。

综上所述,我们通过数据结构维护上述式子的区间(链上)最值,在染色时区间自增,在查询颜色时考虑区间最小值,在清空子树时区间赋值(此处应注意,须赋调整后的 $\operatorname{pre}_x(\operatorname{dep}(x))$ 而非其加上 $-\operatorname{dep}(x)$),就完成了本题。

时间复杂度 $\mathrm{O}(n \log^2 n)$。

Submission #164125980

#include <bits/stdc++.h>
using namespace std;

/* 快读已省略。 */

#define inl inline
#define reint register int
#define newl putchar('\n')
typedef long long ll;
// typedef unsigned long long ull;
// typedef __int128 lll;
// typedef long double llf;
typedef pair <int, int> pint;
#define fst first
#define scd second
#define all(p) begin (p), end (p)

constexpr int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n, m, x, y, ope, siz[N], dep[N], fa[N];
int top[N], dfn[N], owa[N], dtot, hson[N], seq[N];
vector <int> e[N];

void dfs (int x) {
	dep[x] = dep[fa[x]] + 1,
	siz[x] = 1, hson[x] = 0;
	for (const int y : e[x]) {
		dfs (y), siz[x] += siz[y];
		if (siz[hson[x]] < siz[y])
			hson[x] = y;
	}
}
void decomp (int x, int tp) {
	dfn[x] = ++dtot, top[x] = tp;
	seq[dtot] = x;
	if (!hson[x]) { owa[x] = dtot; return; }
	decomp (hson[x], tp);
	for (const int y : e[x])
		if (y != hson[x]) decomp (y, y);
	owa[x] = dtot;
}

inl void gomn (int &x, int y) { x = min (x, y); }
struct seg_tree {
	struct node { int l, r, stag, dtag, mxdep, mn; } t[N<<2];

	#define post(x) (t[x].mn = min (t[x<<1].mn, t[x<<1|1].mn))
	void build (int x, int l, int r) {
		t[x] = { l, r, INF, 0, 0, 0 };
		if (l == r) { t[x].mn =
			-(t[x].mxdep = dep[seq[l]]); return; }
		int mid = l + r >> 1;
		build (x<<1, l, mid);
		build (x<<1|1, mid + 1, r);
		post (x); t[x].mxdep = max
			(t[x<<1].mxdep, t[x<<1|1].mxdep);
	}
	inl void _set (int x, int num) {
		t[x].mn = num - t[x].mxdep,
		t[x].stag = num, t[x].dtag = 0; }
	inl void _inc (int x, int dt) {
		t[x].mn += dt, t[x].dtag += dt; }
	inl void down (int x) {
		int &stag = t[x].stag, &dtag = t[x].dtag;
		if (stag != INF) {
			_set (x<<1, stag), _set (x<<1|1, stag);
			stag = INF; }
		_inc (x<<1, dtag), _inc (x<<1|1, dtag);
		dtag = 0;
	}

	#define FUNCTION(name, inn)\
	void name (int x, int L, int R, int num) {\
		if (t[x].l >= L && t[x].r <= R)\
			return inn (x, num); \
		int mid = t[x].l + t[x].r >> 1; down (x);\
		if (L <= mid) name (x<<1, L, R, num);\
		if (R > mid) name (x<<1|1, L, R, num);\
		post (x);\
	}
	FUNCTION (cover, _set);
	FUNCTION (add, _inc);
	int query (int x, int L, int R) {
		if (t[x].l >= L && t[x].r <= R) return t[x].mn;
		int mid = t[x].l + t[x].r >> 1;
		int res = INF; down (x);
		if (L <= mid) gomn (res, query (x<<1, L, R));
		if (R > mid) gomn (res, query (x<<1|1, L, R));
		return res;
	}
} seg;

inl void cover (int x) {
	int mn = min (-1, seg.query (1,
		dfn[x], dfn[x])), y = fa[x];
	while (y) gomn (mn, seg.query
		(1, dfn[top[y]], dfn[y])-1), y = fa[top[y]];
	seg.cover (1, dfn[x], owa[x], mn += dep[x]);
}
inl bool query (int x) {
	int mn = 0, y = fa[x];
	while (y) gomn (mn, seg.query
		(1, dfn[top[y]], dfn[y])), y = fa[top[y]];
	return seg.query (1, dfn[x], dfn[x]) >= mn;
}

int main () {
	/* CF1017G. The Tree 吴秋实 */
	#ifdef LOCAL
	freopen ("CF1017G.in", "r", stdin);
	freopen ("CF1017G.out", "w", stdout);
	#endif
	
	read (n, m);
	for (y = 2; y <= n; ++y)
		read (x), fa[y] = x, e[x].push_back (y);
	dfs (1), decomp (1, 1);
	seg.build (1, 1, n);
	for (int i = 1; i <= m; ++i) {
		read (ope, x);
		switch (ope) {
			case 1: seg.add (1, dfn[x], owa[x], 1); break;
			case 2: cover (x); break;
			case 3: puts (query (x) ? "black" : "white"); break;
		}
	}

	return 0;
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注