【luogu P4338】【LOJ 2434】历史(LCT)

历史

题目链接:luogu P4338 / LOJ 2434

题目大意

给你一棵树,其中 1 是根,然后给你每个点的 access 次数,要你规划一个 access 的顺序,使得轻重链的切换次数最大。
然后要支持修改,修改会增加一个点的 access 次数。

思路

考虑不修改的时候怎么做。

考虑 DP 求一个子树的最大切换次数。
那除了内部贡献,还有最上面的点,那这个点最好是轮流来,除了第一次都有一个贡献,即 (sum_i-1)。((sum_i) 是子树大小)

那怎么时候会不能有这种贡献,就是当最大的那个子树个数超过了一半,具体来讲是 (2*maxn>sum_i)
那这个时候,你只能尽可能的交错,所以贡献只有 (2*(sum_i-maxn))

然后你再考虑带修改。
那带修改我们如果只有一种贡献方法就好办,但有两种的话就考虑能不能区分开。

而且通过观察我们会发现,因为是加次数,所以它作为最大值的话就只会从第一种到第二种,如果不是最大值可能会让之前的最大值由第二种变回第一种。
那你考虑用 LCT 的实边虚边维护,就只需要枚举到 (1) 路径上的虚边来查看是否要改动。

那这个我们可以变形一下 access 操作来得到。
然后你每次就查看是否要让实边取消,再看是否要让虚边连上,然后维护一些值就可以了。

代码

#include<cstdio>
#define ll long long

using namespace std;

struct node {
	int to, nxt;
}e[800001];
int n, m;
int le[400001], KK, x, y;
ll a[400001], ans;

int re; char c;

int read() {
	re = 0; c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re;
}

int write(ll x) {
	if (x > 9ll) write(x / 10);
	putchar(x % 10 + '0');
}

struct LCT {
	int ls[400001], rs[400001];
	int fa[400001];
	ll sum[400001], val[400001], xs[400001];
	//sum 是子树的匹配个数,val 相当于 a,xs 是虚边连着的大小
	
	void work(int now) {//建出一开始的
		ll maxn = val[now]; int pl = 0;
		sum[now] = val[now];
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != fa[now]) {
				fa[e[i].to] = now;
				work(e[i].to);
				if (sum[e[i].to] > maxn) maxn = sum[e[i].to], pl = e[i].to;
				sum[now] += sum[e[i].to];
			}
		if (2ll * maxn > sum[now]) ans += 2ll * (sum[now] - maxn), rs[now] = pl;
			else ans += sum[now] - 1;
		xs[now] = sum[now] - val[now] - sum[rs[now]];
	}
	
	bool nrt(int now) {
		return ls[fa[now]] == now || rs[fa[now]] == now;
	}
	
	bool lrs(int now) {
		return ls[fa[now]] == now;
	}
	
	void up(int now) {
		sum[now] = sum[ls[now]] + sum[rs[now]] + val[now] + xs[now];
	}
	
	void rotate(int x) {
		int y = fa[x], z = fa[y];
		int b = lrs(x) ? rs[x] : ls[x];
		if (z && nrt(y)) (lrs(y) ? ls[z] : rs[z]) = x;
		if (lrs(x)) rs[x] = y, ls[y] = b;
			else ls[x] = y, rs[y] = b;
		fa[x] = z; fa[y] = x;
		if (b) fa[b] = y;
		up(y);
	}
	
	void Splay(int x) {
		while (nrt(x)) {
			if (nrt(fa[x])) {
				if (lrs(x) == lrs(fa[x])) rotate(fa[x]);
					else rotate(x);
			}
			rotate(x);
		}
		up(x);
	}
	
	ll clac(int now, ll summ, ll maxn) {
		if (rs[now]) return 2ll * (summ - maxn);
		if (val[now] * 2ll > summ) return 2ll * (summ - val[now]);
		return summ - 1; 
	}
	
	void access(int now, ll y) {
		Splay(now);
		
		//一开始的那一段要特殊处理,因为是加 val 不是 xs
		ll summ = sum[now] - sum[ls[now]], maxn = sum[rs[now]];
		ans -= clac(now, summ, maxn);
		sum[now] += y; val[now] += y;
		summ += y;
		if (2ll * maxn <= summ) xs[now] += maxn, rs[now] = 0, maxn = 0;
		ans += clac(now, summ, maxn);
		up(now);
		
		int lst = now; now = fa[now];
		
		for (; now; now = fa[now]) {
			Splay(now);
			
			summ = sum[now] - sum[ls[now]]; maxn = sum[rs[now]];
			ans -= clac(now, summ, maxn);
			sum[now] += y; xs[now] += y;
			summ += y;
			if (2ll * maxn <= summ) xs[now] += maxn, rs[now] = 0, maxn = 0;
			if (2ll * sum[lst] > summ) xs[now] -= sum[lst], rs[now] = lst, maxn = sum[lst];
			ans += clac(now, summ, maxn);
			up(now);
			
			lst = now;
		}
	}
}T;

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

int main() {
//	freopen("read.txt", "r", stdin);
//	freopen("write.txt", "w", stdout);
	
	n = read(); m = read();
	for (int i = 1; i <= n; i++) a[i] = 1ll * read(), T.val[i] = a[i];
	for (int i = 1; i < n; i++) {
		x = read(); y = read();
		add(x, y);
	}
	
	T.work(1);
	
	write(ans);
	putchar('
');
	while (m--) {
		x = read(); y = read();
		a[x] += y;
		T.access(x, y);
		write(ans);
		putchar('
');
	} 
	
	return 0;
}
原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P4338.html