VEXOBEN
Vexoben
Mar 3, 2019
It takes 9 minutes to read this article.

题目连接:ZJOI2018 历史

(刷新以获取数学公式)

题意

给定一棵个节点的树和一个长度为的数组,次操作,每次会将一个增加,在修改后要求输出:如果以为根,对第个节点要进行次LCT的操作(顺序任意),最多可以使轻重边切换多少次.

数据范围:

题解

有一个显然的结论:每个节点轻重边切换次数对于答案的影响是独立的.因为一个节点轻重边切换需要分别对它两个子树中的节点,而在这之间可以不在这个子树中的节点,从而在保证该节点答案最大的同时最大化其它节点的答案.

这样我们就可以处理没有修改的情况了.设子树中一共要的次数为,它的子树中(包括节点本身)最多要的次数为,当时,答案是,否则答案为

怎么处理修改呢?这里出题人就有了一个骚操作.

如果一个节点是的情况,就在它和那个要次数最多的孩子之间连一条重边.修改时,如果经过一条重边是不会影响答案的.同时很容易发现一个点到根的路径上轻边的条数是级别的,因此可以直接树剖+二分做到

复杂度更低的做法是用LCT代替树剖.将每一条重链用Splay维护,用类似于的方式进行修改.不过,它并不会将所有经过的轻边都改为重边.因为不(wo)可(ye)描(bu)述(hui)的原因,复杂度就降低到了

#pragma GCC optimize("2,Ofast,inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 4e5 + 10;

LL ans, a[N];

template<typename T> void read(T &x) {
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	for (x = 0; c >= '0' && c <= '9'; c = getchar())
		x = (x << 3) + (x << 1) + c - '0';
}

namespace LCT {
	int fa[N], hs[N], ch[N][2];
	LL res[N], sum[N], tag[N];

	void Calcans(int x) {
		ans -= res[x];
		if (hs[x]) res[x] = 2 * (sum[x] - sum[hs[x]]);
		else if (a[x] * 2 >= sum[x] + 1) res[x] = 2 * (sum[x] - a[x]);
		else res[x] = sum[x] - 1;
		ans += res[x];
	}

	int Isroot(int x) {
		return (ch[fa[x]][0] != x) && (ch[fa[x]][1] != x);
	}

	int Get(int x) {
		return (ch[fa[x]][1] == x);
	}

	void Pushdown(int x) {
		if (x && tag[x]) {
			sum[x] += tag[x];
			tag[ch[x][0]] += tag[x];
			tag[ch[x][1]] += tag[x];
			tag[x] = 0;
		}
	}

	void Rotate(int x) {
		int y = fa[x], z = fa[y], k = Get(x);
		fa[x] = z;
		if (!Isroot(y)) ch[z][Get(y)] = x;
		fa[ch[x][k ^ 1]] = y;
		ch[y][k] = ch[x][k ^ 1];
		ch[x][k ^ 1] = y;
		fa[y] = x;
	}

	void Pushall(int x) {
		static int top, st[N];
		st[top = 1] = x;
		for (int i = x; !Isroot(i); i = fa[i]) {
			st[++top] = fa[i];
		}
		while (top) Pushdown(st[top--]);
	}
	
	void Splay(int x) {
		Pushall(x);
		for (; !Isroot(x); Rotate(x)) {
			if (!Isroot(fa[x])) {
				Get(x) == Get(fa[x]) ? Rotate(fa[x]) : Rotate(x);
			}
		}
	}

	int Find(int x)  {
		while (ch[x][0]) {
			Pushdown(x);
			x = ch[x][0];
		}
		Pushdown(x);
		return x;
	}

	void Access(int x, int w) {
		int i;
		a[x] += w;
		for (i = 0; x; i = x, x = fa[x]) {
			Splay(x);
			sum[x] += w; tag[ch[x][0]] += w;
			if (hs[x]) {
				Pushall(hs[x]);
				if (sum[hs[x]] * 2 < sum[x] + 1) {
					hs[x] = 0;
					ch[x][1] = 0;
				}
			}
			int t = Find(i);
			if (sum[t] * 2 >= sum[x] + 1) {
				hs[x] = t;
				ch[x][1] = i;
			}
			Calcans(x);
		}
	}
}

namespace Prework {
	int E, fir[N], nex[N << 1], arr[N << 1];

	inline void Add_Edge(int x, int y) {
		nex[++E] = fir[x];
		fir[x] = E; arr[E] = y;
	}

	void dfs(int x, int fa) {
		if (fa) LCT::fa[x] = fa;
		LCT::sum[x] = a[x];
		for (int i = fir[x]; i; i = nex[i]) {
			if (arr[i] == fa) continue;
			dfs(arr[i], x);
			LCT::sum[x] += LCT::sum[arr[i]];
		}
		for (int i = fir[x]; i; i = nex[i]) {
			if (arr[i] == fa) continue;
			if (LCT::sum[arr[i]] * 2 >= LCT::sum[x] + 1) {
				LCT::hs[x] = arr[i];
				LCT::ch[x][1] = arr[i];
			}
		}
		LCT::Calcans(x);
	}

	void main(int &n, int &m) {
		read(n); read(m);
		for (int i = 1; i <= n; ++i) {
			read(a[i]);
		}
		for (int i = 1; i < n; ++i) {
			int x, y;
			read(x); read(y);
			Add_Edge(x, y);
			Add_Edge(y, x);
		}
		dfs(1, 0);
		printf("%lld\n", ans);
	}
}

int main() {
	static int n, m;
	Prework::main(n, m);
	while (m--) {
		int x, w;
		read(x); read(w);
		LCT::Access(x, w);
		printf("%lld\n", ans);
	}
	return 0;
}