Codeforces 1017G - The Tree(树链剖分+思维)

Codeforces 题面传送门 & 洛谷题面传送门

好题。

首先这个修改操作看起来就有点鸡肋,我们考虑将其进行转化。我们不妨从修改对询问的贡献的角度来看待这些修改,那么不难发现,对于一个点 \(x\),一个点 \(y\) 的修改能够使 \(x\) 变成黑色,当且仅当 \(y\)\(x\) 的祖先,并且 \(x\)\(y\)​ 路径上所有点的操作次数之和大于 \(x,y\) 之间的距离,这样 \(y\) 的修改才能影响到 \(x\)。可以注意到,我们可以将模型进行如下转化:每个点有一个点权,初始是 \(-1\),每次操作将某个点的点权加 \(1\),或者询问一个点 \(x\) 到根路径上是否存在另一个点 \(y\),满足 \(x,y\) 之间路径上的点权之和 \(\ge 0\)。不难发现该问题和原问题是等价的,线段树 + 树链剖分维护后缀最大值即可解决。

如果没有 \(2\) 操作那么问题这么做就解决了,接下来考虑怎样处理 \(2\) 操作,一个很直观的想法是将 \(x\) 子树内所有点权值设为 \(-1\),但是问题真的就这么简单吗?不难发现有可能出现这样的情况:在 clear \(x\) 的子树之前,\(x\) 的父亲进行了很多次操作导致点权很大,此时你即便把 \(x\) 权值设为 \(-1\)\(x\) 到根节点路径上权值的最大前缀和还是 \(\ge 0\)。怎么办呢?由于我们强制要将 \(x\) 的权值设为 \(-1\),因此我们要适当减小 \(x\) 点的权值,使得 \(x\) 点到根节点路径上的最大前缀和 \(=-1\),很明显,我们需要让 \(x\) 点的权值减去 \(\text{此时 }x\text{ 点到根节点路径上的最大前缀和}+1\),直接线段树上单点修改即可。

时间复杂度 \(n\log^2n\)

const int MAXN = 1e5;
const int INF = 0x3f3f3f3f;
int n, qu, hd[MAXN + 5], to[MAXN + 5], nxt[MAXN + 5], ec = 0;
void adde(int u, int v) {to[++ec] = v; nxt[ec] = hd[u]; hd[u] = ec;}
int fa[MAXN + 5], siz[MAXN + 5], wson[MAXN + 5], dep[MAXN + 5];
int top[MAXN + 5], dfn[MAXN + 5], edt[MAXN + 5], tim = 0;
void dfs1(int x) {
	siz[x] = 1;
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e]; dep[y] = dep[x] + 1; dfs1(y); siz[x] += siz[y];
		if (siz[y] > siz[wson[x]]) wson[x] = y;
	}
}
void dfs2(int x, int tp) {
	top[x] = tp; dfn[x] = ++tim; if (wson[x]) dfs2(wson[x], tp);
	for (int e = hd[x]; e; e = nxt[e]) if (to[e] != wson[x]) dfs2(to[e], to[e]);
	edt[x] = tim;
}
struct dat {
	int sum, mxsuf;
	dat() {sum = mxsuf = 0;}
	friend dat operator + (dat x, dat y) {
		dat res; res.sum = x.sum + y.sum;
		res.mxsuf = max(y.mxsuf, y.sum + x.mxsuf);
		return res;
	}
};
struct node {int l, r, tg; dat v;} s[MAXN * 4 + 5];
void pushup(int k) {s[k].v = s[k << 1].v + s[k << 1 | 1].v;}
void build(int k, int l, int r) {
	s[k].l = l; s[k].r = r; if (l == r) return s[k].v.sum = s[k].v.mxsuf = -1, void();
	int mid = l + r >> 1; build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r); pushup(k);
}
void tag(int k) {s[k].tg = 1; s[k].v.sum = -(s[k].r - s[k].l + 1); s[k].v.mxsuf = -1;}
void pushdown(int k) {if (s[k].tg) tag(k << 1), tag(k << 1 | 1), s[k].tg = 0;}
void assign(int k, int l, int r) {
	if (l <= s[k].l && s[k].r <= r) return tag(k), void(); pushdown(k); int mid = s[k].l + s[k].r >> 1;
	if (r <= mid) assign(k << 1, l, r); else if (l > mid) assign(k << 1 | 1, l, r);
	else assign(k << 1, l, mid), assign(k << 1 | 1, mid + 1, r); pushup(k);
}
void modify(int k, int p, int v) {
	if (s[k].l == s[k].r) return s[k].v.sum += v, s[k].v.mxsuf += v, void(); pushdown(k);
	int mid = s[k].l + s[k].r >> 1; (p <= mid) ? modify(k << 1, p, v) : modify(k << 1 | 1, p, v); pushup(k);
}
dat query(int k, int l, int r) {
	if (l <= s[k].l && s[k].r <= r) return s[k].v; pushdown(k); int mid = s[k].l + s[k].r >> 1;
	if (r <= mid) return query(k << 1, l, r); else if (l > mid) return query(k << 1 | 1, l, r);
	else return query(k << 1, l, mid) + query(k << 1 | 1, mid + 1, r);
}
int askmx(int x) {
	int csum = 0, mx = -INF;
	while (x) {
		dat D = query(1, dfn[top[x]], dfn[x]);
		chkmax(mx, csum + D.mxsuf); csum += D.sum;
		x = fa[top[x]];
	}
//	printf("%d\n", mx);
	return mx;
}
int main() {
	scanf("%d%d", &n, &qu);
	for (int i = 2; i <= n; i++) scanf("%d", &fa[i]), adde(fa[i], i);
	dfs1(1); dfs2(1, 1); build(1, 1, n);
//	for (int i = 1; i <= n; i++) printf("%d%c", top[i], " \n"[i == n]);
//	for (int i = 1; i <= n; i++) printf("%d %d\n", dfn[i], edt[i]);
	while (qu--) {
		int opt, x; scanf("%d%d", &opt, &x);
		if (opt == 1) modify(1, dfn[x], 1);
		else if (opt == 2) {
			if (dfn[x] != edt[x]) assign(1, dfn[x] + 1, edt[x]);
			modify(1, dfn[x], -(askmx(x) + 1));
		} else printf("%s\n", (askmx(x) >= 0) ? "black" : "white");
	}
	return 0;
}
posted @ 2022-01-02 23:24  tzc_wk  阅读(105)  评论(0)    收藏  举报