洛谷 P4338 - [ZJOI2018]历史(LCT)

题面传送门

一道不错的 LCT 练手题。

首先考虑对于没有修改的情况怎么处理。注意到对于一个 \(u\),如果我们把 \(u\) 的每个儿子的子树染上一种颜色,把 \(u\) 点也染上一种颜色,那么当我们 access \(u\) 子树内某个点 \(v\) 时,点 \(u\) 会对答案产生 \(1\) 的贡献,当且仅当 \(u\) 子树内上一个被 access 的点与 \(v\) 不是同一个颜色的,因此如果除了 \(1\) 节点之外,每个点都是叶子,那么问题就转化为一个经典问题:有 \(n\) 种颜色,第 \(i\) 种颜色的点有 \(a_i\) 个,现在要将这 \(\sum\limits_{i=1}^na_i\) 个点排成一行,最大化相邻异色点数。这是一道经典题,根据经典贪心结论,上面的问题的答案为 \(\min(\sum\limits_{i=1}^na_i-1,(\sum\limits_{i=1}^na_i-\max\limits_{i=1}^na_i)\times 2)\)。那如果图不是一张菊花怎么办呢?其实也好办,考虑如何钦定 \(u\) 子树内点的 access 顺序:我们先递归钦定所有儿子子树内的点的 access 顺序,然后将 \(u\) 所有儿子对应的子树看作一个颜色,设 \(u\) 点的儿子个数为 \(c\),那么我们根据上面的经典结论算出将 \(u\) 子树内的点排成一排的最优方案,同一颜色内部 access 顺序则按照我们之前对每个儿子的子树钦定的顺序安排,如此递归下去即可,这就告诉我们,每个点的贡献是可以独立计算的,一遍 DFS 即可解决。具体来说,设 \(f(a_1,a_2,\cdots,a_n)=\min(\sum\limits_{i=1}^na_i-1,(\sum\limits_{i=1}^na_i-\max\limits_{i=1}^na_i)\times 2)\),点 \(u\)\(c_i\) 个儿子 \(son_{u,1},son_{u,2},\cdots,son_{u,c_u}\)\(u\) 子树内所有点的 \(a\) 之和为 \(sum_u\),那么答案等于 \(\sum\limits_{i=1}^nf(sum_{son_{u,1}},sum_{son_{u,2}},\cdots,sum_{son_{u,c_u}},a_u)\)

接下来考虑带上修改如何处理,可以发现这个问题有点链剖分的意味,因为对于每个点 \(u\),根据上面的和式,\(u\) 点产生的贡献只与 \(sum_u\)\(u\) 的儿子中 \(sum\) 最大的有关,因此如果我们称 \(u\) 的儿子中 \(sum\) 最大的为“重儿子”,那么就形成了一个“带权重链剖分”模型。因此我们考虑对于一个点 \(u\),记其儿子中 \(sum\) 最大的为 \(wson_u\),那么如果 \(sum_{wson_u}\times 2>sum_u\),我们就连边 \(u\to wson_u\),这样显然会形成一条条祖先 \(\to\) 后代的链,更具体地,每个点到根路径上最多只有 \(\log nV\) 条重链,这个可以用类似于重链剖分的方法分析出来。而当我们修改一个点 \(u\) 的点权时,我们考虑 \(u\) 到根节点路径上一条重链,那么不难发现,重链中间的点的贡献不会发生变化,唯一可能会发生变化是链顶节点,这启发我们用 LCT 维护每条链,每次修改暴力扣掉原链顶的答案,修改之后再加入新的链顶的答案并更新链剖分的结构。

能做到 \(n\log nV\),不过下面的代码是 \(n\log n\log nV\) 的:

const int MAXN = 5e5;
int n, qu, hd[MAXN + 5], to[MAXN * 2 + 5], nxt[MAXN * 2 + 5], ec, fa[MAXN + 5];
void adde(int u, int v) {to[++ec] = v; nxt[ec] = hd[u]; hd[u] = ec;}
int dfn[MAXN + 5], edt[MAXN + 5], tim, wson[MAXN + 5];
ll res = 0, a[MAXN + 5];
ll t[MAXN + 5];
void add(int x, ll v) {for (int i = x; i <= n; i += (i & (-i))) t[i] += v;}
ll query(int x) {ll ret = 0; for (int i = x; i; i &= (i - 1)) ret += t[i]; return ret;}
ll qry(int x) {return (!x) ? 0 : (query(edt[x]) - query(dfn[x] - 1));}
ll clc(int x) {return min(qry(x) - 1, (qry(x) - max(a[x], qry(wson[x]))) * 2);}
void dfs(int x, int f) {
	dfn[x] = ++tim; add(tim, a[x]); fa[x] = f;
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e]; if (y == f) continue; dfs(y, x);
		if (qry(y) > qry(wson[x])) wson[x] = y;
	}
	edt[x] = tim; res += clc(x);
}
struct node {int ch[2], f;} s[MAXN + 5];
int ident(int k) {return ((s[s[k].f].ch[0] == k) ? 0 : ((s[s[k].f].ch[1] == k) ? 1 : -1));}
void connect(int k, int f, int op) {s[k].f = f; if (~op) s[f].ch[op] = k;}
void rotate(int x) {
	int y = s[x].f, z = s[y].f, dx = ident(x), dy = ident(y);
	connect(s[x].ch[dx ^ 1], y, dx); connect(y, x, dx ^ 1); connect(x, z, dy);
}
void splay(int k) {
	while (~ident(k)) {
		if (!~ident(s[k].f)) rotate(k);
		else if (ident(k) == ident(s[k].f)) rotate(s[k].f), rotate(k);
		else rotate(k), rotate(k);
	}
}
int qrymn(int k) {while (s[k].ch[0]) k = s[k].ch[0]; return k;}
int main() {
	scanf("%d%d", &n, &qu);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1, u, v; i < n; i++) scanf("%d%d", &u, &v), adde(u, v), adde(v, u);
	dfs(1, 0); printf("%lld\n", res);
	for (int i = 1; i <= n; i++) s[i].f = fa[i];
	for (int i = 1; i <= n; i++) if (wson[i] && qry(wson[i]) * 2 > qry(i))
		s[i].ch[1] = wson[i];
	while (qu--) {
		int x, v; scanf("%d%d", &x, &v); map<int, int> vis;
		for (int y = x; y; y = s[y].f) splay(y), res -= clc(y);
		add(dfn[x], v); a[x] += v;
		int pre = 0, prex = 0;
		for (int y = x; y; prex = y, y = s[y].f) {
			splay(y); //printf("! %d\n", y);
			if (pre && qry(pre) > qry(wson[y])) {
				wson[y] = pre; s[y].ch[1] = prex;
			}
			if (qry(wson[y]) * 2 <= qry(y)) s[y].ch[1] = 0;
			res += clc(y); pre = qrymn(y);
		}
		printf("%lld\n", res);
	}
	return 0;
}
/*
5 8
1 1 1 1 1
1 2
2 3
3 4
4 5
3 10
4 100
5 1000
2 10000
3 100000
1 200000
3 300000
5 400000
*/
posted @ 2022-03-05 19:05  tzc_wk  阅读(74)  评论(0)    收藏  举报