[ZJOI2018] 历史 题解

你谷 link

这个题说模板挺模板的,但是其实也很有思维,这里来讲讲看。

简化题意,给定一棵 LCT,初始都是虚边,给定每个点的 access 操作次数,求一个操作顺序使得虚实边转换次数最多。

首先考虑如果不存在修改怎么做,我们考虑按每个点分别考虑,我们发现当相邻两次操作在不同的子树中(或者一个在子树中另一个是自己)都可以产生贡献,我们可以着手构造,然后发现如果没有一个子树(或自己)的操作次数和大于一半,都可以构造满,否则就是剩下的两倍,即 \(\min\{S-1,2(S-\max\{a_x,\max_{y\in\operatorname{son}(x)}S_y\})\}\),这样我们就能得到没有修改操作的分。

接下来是修改操作,首先显然这种修改只会影响被修改的点到根的路径,我们考虑对这条路径进行操作。

观察上式,我们发现有一个临界点,就是 \(S\)\(\max_{y\in\operatorname{son}(x)}S_y\}\),的关系,即有没有一个子树的大小超过一半,另一个特性是这个子树只可能存在一个,我们称这个子树对应的儿子为该点的“重儿子”,与重剖不同的是,重剖中的重儿子是子树最大的儿子,而这里不是,这里是子树大小超过该树一半的儿子,且这个重儿子也可以是自己(自己的值超过子树的一半),当然也可以没有。

类比重剖,重儿子组成了一条重链,我们发现一次修改最多只会在每条重链中影响一个点,因为重儿子变大不会影响上式的值,这是保证我们时间复杂度的关键,重剖中每个点到根只会经过至多 \(\log n\) 条重链,因为每经过一个轻儿子子树大小就会减半,这其实就是点权等于 \(1\) 的特殊情况,在点权不为 \(1\) 时同样成立,最多经过 \(\log\sum_{i=1}^n a_i\) 条重链,证明同上。

接下来问题就转化成一下几点:

  1. 动态维护重链的修改;
  2. 查询子树和与重儿子信息。

这其实就很显然了,可以直接套 LCT 解决,时间复杂度大约是 \(\mathcal O\left(n\log\sum_{i=1}^n a_i\right)\),不是很会 LCT 的时间复杂度证明,如有错误欢迎指正。

c++ 代码
#include<bits/stdc++.h>

using namespace std;

#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高

typedef long long LL;
typedef unsigned long long ULL;

typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second

const int N = 400010;

int n, m;
LL ans;
basic_string<int> G[N];

struct LCT {
#define fa(p) tr[p].fa
#define ch(p, o) tr[p].ch[o]
#define ls(p) ch(p, 0)
#define rs(p) ch(p, 1)
#define ty(p) (rs(fa(p)) == p)
	struct Node { LL x, is, s; int ty, fa, ch[2]; } tr[N];
	Marisa isRt(int p) { return ls(fa(p)) ^ p && rs(fa(p)) ^ p; }
	Reimu pushUp(int p) { tr[p].s = tr[p].x + tr[p].is + tr[ls(p)].s + tr[rs(p)].s; }
	Reimu rotate(int p) {
		int fp = fa(p), ffp = fa(fp), o = ty(p), q = ch(p, !o);
		if (!isRt(fp)) ch(ffp, ty(fp)) = p; ch(ch(p, !o) = fp, o) = q;
		if (q) fa(q) = fp; fa(fa(fp) = p) = ffp;
		pushUp(fp); pushUp(p);
	}
	Reimu splay(int p) { for (int fp; !isRt(p); rotate(p)) if (!isRt(fp = fa(p))) rotate(ty(fp) ^ ty(p) ? p : fp); }
	Reimu access(int p, int delta) {
		for (int q = 0; p; p = fa(q = p)) {
			splay(p);
			LL s = tr[p].s - tr[ls(p)].s;
			ans -= tr[p].ty ? s - (~tr[p].ty ? tr[p].x : tr[rs(p)].s) << 1 : s - 1;
			s += delta; tr[p].s += delta; (q ? tr[p].is : tr[p].x) += delta;
			if (tr[q].s << 1 > s) tr[p].is += tr[rs(p)].s, tr[p].is -= tr[rs(p) = q].s;
			if (tr[rs(p)].s << 1 > s) tr[p].ty = -1, ans += s - tr[rs(p)].s << 1;
			else {
				tr[p].is += tr[rs(p)].s; rs(p) = 0;
				if (tr[p].x << 1 > s) tr[p].ty = 1, ans += s - tr[p].x << 1;
				else tr[p].ty = 0, ans += s - 1;
			}
		}
	}
	Reimu init(int x) {
		int mx = 0;
		for (auto y: G[x]) if (y ^ fa(x)) {
			fa(y) = x;
			init(y);
			tr[x].is += tr[y].s;
			if (tr[y].s > (mx ? tr[mx].s : tr[x].x)) mx = y;
		}
		LL v = mx ? tr[mx].s : tr[x].x;
		if (v << 1 > (tr[x].s = tr[x].is + tr[x].x)) {
			ans += tr[x].s - v << 1;
			if (mx) tr[x].ty = -1, tr[x].is -= tr[rs(x) = mx].s;
			else tr[x].ty = 1;
		} else ans += tr[x].s - 1;
	}
#undef fa
#undef ch
#undef ls
#undef rs
#undef ty
} lct;

int main() {
	ios::sync_with_stdio(false); cin.tie(nullptr);
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) cin >> lct.tr[i].x;
	for (int i = 1, x, y; i < n; ++i) cin >> x >> y, G[x] += y, G[y] += x;
	lct.init(1); cout << ans << '\n';
	while (m--) {
		int x, y; cin >> x >> y;
		lct.access(x, y);
		cout << ans << '\n';
	}
	return 0;
}
posted @ 2022-06-20 15:48  老莽莽穿一切  阅读(47)  评论(0编辑  收藏  举报