P4332 [SHOI2014]三叉神经树

要求维护一棵树:

  • 每个点有 33 个端口,分为输入端和输出端(连向父亲)。输出端的个数 1\leq 1
  • 如果一个点输入端权值为 11 的个数 2\geq 2,那么这个点的权值为 11,否则为 00
  • 支持动态修改叶子节点,修改后询问根节点的权值。

1n5×1051 \leq n \leq 5\times 10^5,时限 4s4\text{s},空限 250MB250\text{MB}

sol

可以发现,每一次修改叶子节点后,权值改变的点的集合一定是一条自上而下的链,且这条链开头不定,但结尾一定是被修改点。

然后我们还可以发现(此处记 sumisum_i 表示 ii 点的三个儿子中,权值为 11 的点得个数):

  • 若被修改点是从 11 改成 00 的,那么从被修改点开始向上,只有 sum=2sum=2 的点的颜色改变(从 11 变成 00)。
  • 若被修改点是从 00 改成 11 的,那么从被修改点开始向上,只有 sum=1sum=1 的点的颜色改变(从 00 变成 11)。

那么,求出一个点子树内最深的 sum1sum \ne 1sum2sum \ne 2 的点即为修改这个点的子树内的叶子节点的链头。

那么怎么维护?

O(nlog3n)\mathcal O(n \log^3 n)

考虑树链剖分,用线段树维护区间最小值和最大值,当且仅当某个区间的最小值和最大值均为 1122 时才表示这一链的点的权值均为 1122

至于链头,则可以通过二分或倍增来找,总时间复杂度为 O(nlog3n)\mathcal O(n \log^3 n)

具体地,二分 O(logn)\mathcal O(\log n),跳重链 O(logn)\mathcal O(\log n),线段树查询 O(logn)\mathcal O(\log n)

O(nlog2n)\mathcal O(n \log^2 n)

LCT 可以 O(logn)\mathcal O(\log n) 实现查询链上最大最小值。

故总时间复杂度优化为 O(nlog2n)\mathcal O(n \log^2 n)

O(nlogn)\mathcal O(n \log n)

考虑更直接的方法,维护一个点的信息。

讨论一下,修改 push_uppush_down 函数即可。

总时间复杂度为 O(nlogn)\mathcal O(n \log n)

许多细节。

具体参考代码。

#include <bits/stdc++.h>

#define lc c[x][0]
#define rc c[x][1]

using namespace std;

inline int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    return x * f;
}

const int _ = 1.5e6 + 10;

struct LCT
{
	int f[_], c[_][2], id[_][3], v[_], s[_], st[_];
	int lz[_];
	inline bool isroot(int x)
	{
		return c[f[x]][0] == x || c[f[x]][1] == x;
	}
	inline void push_up(int x)
	{
		id[x][1] = id[rc][1];
		id[x][2] = id[rc][2];
		if(!id[x][1])
		{
			if(s[x] != 1)
				id[x][1] = x;
			else
				id[x][1] = id[lc][1];
		}
		if(!id[x][2])
		{
			if(s[x] != 2)
				id[x][2] = x;
			else
				id[x][2] = id[lc][2];
		}
	}
	inline void push_(int t, int x)
	{
		s[t] += x, v[t] = s[t] > 1;
		swap(id[t][1], id[t][2]);
		lz[t] += x;
	}
	inline void push_down(int x)
	{
		if(lz[x])
		{
			if(lc)
				push_(lc, lz[x]);
			if(rc)
				push_(rc, lz[x]);
			lz[x] = 0;
		}
	}
	inline void rotate(int x)
	{
		int y = f[x], z = f[y], k = c[y][1] == x, w = c[x][!k];
		if(isroot(y))
			c[z][c[z][1] == y] = x;
		c[x][!k] = y, c[y][k] = w;
		if(w)
			f[w] = y;
		f[y] = x, f[x] = z;
		push_up(y);
	}
	inline void splay(int x)
	{
		int y = x, z = 0;
		st[++z] = y;
		while(isroot(y))
			st[++z] = y = f[y];
		while(z)
			push_down(st[z--]);
		while(isroot(x))
		{
			y = f[x], z = f[y];
			if(isroot(y))
				rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
			rotate(x);
		}
		push_up(x);
	}
	inline void access(int x)
	{
		for(int y = 0; x; x = f[y = x])
			splay(x), rc = y, push_up(x);
	}
} tr;

int n, m;

#define pr printf

int tot, head[_], to[_ << 1], nxt[_ << 1];

void add(int u, int v)
{
	to[++tot] = v, nxt[tot] = head[u], head[u] = tot;
}

void dfs(int x, int f)
{
	tr.s[x] = 0;
	for(int i = head[x], v; i; i = nxt[i])
	{
		v = to[i];
		if(v == f) continue;
		dfs(v, x);
		tr.s[x] += tr.v[v];
	}
	if(x <= n)
		tr.v[x] = tr.s[x] > 1;
}

signed main()
{
	n = read();
	int x;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= 3; ++j)
		{
			x = read();
			tr.f[x] = i;
			add(x, i), add(i, x);
		}
	for(int i = n + 1; i <= n * 3 + 1; ++i)
		tr.v[i] = read();
	dfs(1, 0);
	m = read();
	int lst, w, addtag, ans = tr.v[1];
	while(m--)
	{
		lst = read(), x = tr.f[lst];
		addtag = tr.v[lst] ? -1 : 1;
		tr.access(x), tr.splay(x);
		w = tr.id[x][tr.v[lst] ? 2 : 1];
		if(w)
		{
			tr.splay(w);
			tr.push_(tr.c[w][1], addtag);
			tr.push_up(w);
			tr.s[w] += addtag, tr.v[w] = tr.s[w] > 1;
			tr.push_up(w);
		}
		else
		{
			ans ^= 1;
			tr.push_(x, addtag);
			tr.push_up(x);
		}
		tr.v[lst] ^= 1;
		pr("%d\n", ans);
	}
	return 0;
}
posted @ 2022-03-23 13:58  蒟蒻orz  阅读(1)  评论(0编辑  收藏  举报  来源