「CF1017G」The Tree

cqbzljh發表於2024-04-30

這是一道非常 NB 的題意轉化題.

Introduction

給定一棵樹, 維護以下3個操作:

  1. 1 x 表示如果節點 \(x\) 為白色, 則將其染黑. 否則對這個節點的所有兒子遞迴進行相同操作;
  2. 2 x 表示將以節點 \(x\)\(root\) 的子樹染白;
  3. 3 x 表示查詢節點 \(x\) 的顏色.

Striking Ideas

修改複雜度遠遠高於詢問複雜度. 考慮平衡操作.

先不考慮操作二.
考慮將對兒子遞迴轉化成對該節點進行資訊修改, 可以發現如果考慮對一個已染色的節點再次染色, 相當於它被染色了 \(2\) 次, 也就等價於它的兒子節點都被染色.
為此, 我們發現, 如果將染黑一個節點表示為將該節點的權值 \(+1\), 則有如果一個節點是黑色, 則一定存在一個祖先節點 (可以是它本身), 使得路徑上的權值之和大於等於距離. 為了方便做題, 我們將初始權值設定為 \(-1\), 則只需要最大字尾和 \(\geq 0\) 即可.

現在考慮操作二.
如果讓子樹染白, 即使子樹內所有的節點的最大字尾和為 \(-1\), 可以先直接把子樹內節點權值賦為 \(-1\), 再給子樹根節點 \(u\) 的權值減少 \(v\), 使得 \(u\) 的最大字尾和為 \(-1\), 容易得到 \(v = query(u) + 1\), 其中 \(query(u)\) 表示最大字尾和.

Algorithm

直接樹鏈剖分, 再用線段樹維護最大字尾和即可. 時間複雜度 \(\mathcal O(n\log ^2n)\).

Code

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

using ci = const int;

using i64 = long long;
using u64 = unsigned i64;
using u32 = unsigned;

const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;

int n, m;
vector<int> e[N];

int fa[N], siz[N], dep[N], son[N], top[N], dfn[N], rnk[N], tcnt;

void dfs1(int u) {
	siz[u] = 1;
	for (ci &v : e[u]) {
		if (v == fa[u]) continue;
		fa[v] = u;
		dep[v] = dep[u] + 1;
		dfs1(v);
		siz[u] += siz[v];
		if (siz[v] > siz[son[u]])
			son[u] = v;
	}
}

void dfs2(int u) {
	rnk[dfn[u] = ++tcnt] = u;
	if (son[fa[u]] != u) top[u] = u;
	else top[u] = top[fa[u]];

	if (son[u]) dfs2(son[u]);
	for (ci &v : e[u]) if (!dfn[v]) dfs2(v);
}

struct Node {
	int v, s;
	bool la;
	Node() {}
	Node(int _v, int _s, bool _la) : v(_v), s(_s), la(_la) {}

	inline Node operator+(const Node &o) const {
		return Node(max(v + o.s, o.v), s + o.s, 0);
	}
	inline void operator+=(const Node &o) {
		*this = *this + o;
	}
} tr[N << 2];

#define L (p << 1)
#define R (p << 1 | 1)

void pushdown(int p, int l, int r) {
	if (!tr[p].la) return;
	int mid = l + r >> 1;
	tr[L] = Node(-1, -(mid - l + 1), 1);
	tr[R] = Node(-1, -(r - mid), 1);
	tr[p].la = 0;
}

void build(int p, int l, int r) {
	if (l == r) {
		tr[p] = Node(-1, -1, 0);
		return;
	}

	int mid = l + r >> 1;
	build(L, l, mid);
	build(R, mid + 1, r);
	tr[p] = tr[L] + tr[R];
}

void cover(int p, int l, int r, ci &al, ci &ar) {
	if (al <= l && ar >= r) {
		tr[p] = Node(-1, -(r - l + 1), 1);
		return;
	}	
	pushdown(p, l, r);

	int mid = l + r >> 1;
	if (al <= mid) cover(L, l, mid, al, ar);
	if (ar > mid) cover(R, mid + 1, r, al, ar);
	tr[p] = tr[L] + tr[R];
}

void add(int p, int l, int r, ci &x, ci &y) {
	if (l == r) {
		tr[p].v += y;
		tr[p].s += y;
		return;
	}
	pushdown(p, l, r);

	int mid = l + r >> 1;
	if (x <= mid) add(L, l, mid, x, y);
	else add(R, mid + 1, r, x, y);
	tr[p] = tr[L] + tr[R];
}

Node query(int p, int l, int r, ci &al, ci &ar) {
	if (al <= l && ar >= r) return tr[p];
	pushdown(p, l, r);

	int mid = l + r >> 1;
	Node num(-inf, 0, 0);
	if (al <= mid) num += query(L, l, mid, al, ar);
	if (ar > mid) num += query(R, mid + 1, r, al, ar);
	return num;
}

#undef L
#undef R

inline int ask(int u) {
	Node num(-inf, 0, 0);
	while (u) {
		num = query(1, 1, n, dfn[top[u]], dfn[u]) + num;
		u = fa[top[u]];
	}
	return num.v;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
#endif

	scanf("%d %d", &n, &m);
	for (int i = 2, x; i <= n; ++i) {
		scanf("%d", &x);
		e[x].push_back(i);
	}

	dfs1(1);
	dfs2(1);

	build(1, 1, n);

	while (m--) {
		static int op, x;
		scanf("%d %d", &op, &x);

		switch (op) {
			case 1: add(1, 1, n, dfn[x], 1); break;
			case 2:
				cover(1, 1, n, dfn[x], dfn[x] + siz[x] - 1);
				add(1, 1, n, dfn[x], -ask(x) - 1); break;
			case 3: puts(ask(x) >= 0 ? "black" : "white"); break;
		}
	}

	return 0;
}