【luogu P4751】【模板】“动态DP“&动态树分治(加强版)(全局平衡二叉树)

【模板】“动态DP”&动态树分治(加强版)

题目链接:luogu P4751

题目大意

给你一棵树,点带权,每次操作修改点权,要你求最大权独立集的权值大小。

思路

这个东西就是动态 DP,至于动态 DP 是什么这道题怎么做应该都要知道(不然你做加强版干嘛

然后我们发现用树剖的话太慢啦,看看有什么别的方法。
然后你会发现树剖能搞的话还有一个东西多半也可以,LCT!
然后搞是可以搞,但是 LCT 的常数巨大,卡不过去(当然如果卡常技巧够好像也可以过)

不过我们会发现我们树的形态是固定的,所以我们动态树不一定要动态。
于是我们就想一个类似 LCT 的,但是它是静态的,以减少常数。
那我们就类似的方法,重链剖分,重链搞一棵树,然后之间用轻边连着。
那为了要减少深度,我们就要弄一种方法来构造重链的树。

然后有一种方法就是你重链每个点有一个值叫做它这个点加上它这个点连出的轻边下面树的大小,那我们对于一条链我们找到以这个值为权的链重心作为根,然后两半递归下去,就可以构造出深度为 log ⁡ n \log n logn 的啦!
然后我们修改的时候就可以暴力往上跳,然后修改当前位置维护树上信息,然后如果这个点是当前树的根你就自己手动转移一下。

然后加个快读快输什么就可以啦!

代码

#include<cstdio>
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

const int N = 1e6 + 1;
struct line {
	int to, nxt;
}e[N << 1];
int n, m, a[N], lstans, le[N], KK;

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

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

int max(int x, int y) {
	return x > y ? x : y;
}

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

int sz[N], g[N][2], son[N], root;

void dfs0(int now, int father) {
	sz[now] = 1; g[now][1] = a[now];
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to ^ father) {
			dfs0(e[i].to, now); sz[now] += sz[e[i].to];
			if (sz[e[i].to] > sz[son[now]]) son[now] = e[i].to;
			g[now][0] += max(g[e[i].to][0], g[e[i].to][1]);
			g[now][1] += g[e[i].to][0];
		}
}

void dfs1(int now, int father) {
	if (son[now]) {
		g[now][0] -= max(g[son[now]][0], g[son[now]][1]);
		g[now][1] -= g[son[now]][0];
	}
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to ^ father)
			dfs1(e[i].to, now);
}

struct matrix {
	int a[2][2];
	
	matrix() {
		a[0][0] = a[0][1] = a[1][0] = a[1][1] = -INF;
	}
	
	matrix operator *(matrix y) {
		matrix re;//循环展开
		re.a[0][0] = max(re.a[0][0], a[0][0] + y.a[0][0]);
		re.a[0][0] = max(re.a[0][0], a[0][1] + y.a[1][0]);
		re.a[0][1] = max(re.a[0][1], a[0][0] + y.a[0][1]);
		re.a[0][1] = max(re.a[0][1], a[0][1] + y.a[1][1]);
		re.a[1][0] = max(re.a[1][0], a[1][0] + y.a[0][0]);
		re.a[1][0] = max(re.a[1][0], a[1][1] + y.a[1][0]);
		re.a[1][1] = max(re.a[1][1], a[1][0] + y.a[0][1]);
		re.a[1][1] = max(re.a[1][1], a[1][1] + y.a[1][1]);
		return re;
	}
	
	int getans() {
		return max(a[0][0], a[1][0]);
	}
}one;

struct BST {
	matrix Val[N], Sum[N];
	int ls[N], rs[N], fa[N], sta[N], szz[N];
	bool nrt[N];
	
	void Init_nrt() {
		for (int i = 1; i <= n; i++) nrt[i] = ls[fa[i]] == i || rs[fa[i]] == i;
	}
	
	void up(int now) {
		Sum[now] = Sum[ls[now]] * Val[now] * Sum[rs[now]];
	}
	
	int buildT(int l, int r) {
		if (l > r) return 0;
		static int tot; tot = 0; for (int i = l; i <= r; i++) tot += szz[sta[i]];//全部的和
		for (int i = l, now = szz[sta[i]]; i <= r; i++, now += szz[sta[i]])
			if (now * 2 >= tot) {//找到重心的位置
				ls[sta[i]] = buildT(l, i - 1); rs[sta[i]] = buildT(i + 1, r);
				fa[ls[sta[i]]] = fa[rs[sta[i]]] = sta[i]; up(sta[i]); return sta[i]; 
			}
	}
	
	void Make_val(int now) {
		Val[now].a[0][0] = Val[now].a[0][1] = g[now][0];
		Val[now].a[1][0] = g[now][1];
	}
	
	int build(int now, int fr) {
		for (int i = now; i; fr = i, i = son[i]) {
			for (int j = le[i]; j; j = e[j].nxt)
				if (e[j].to ^ fr && e[j].to ^ son[i])//枚举新的重链的头
					fa[build(e[j].to, i)] = i;
			Make_val(i);
		}
		sta[0] = 0;
		for (int i = now; i; i = son[i])//建这条重链
			sta[++sta[0]] = i, szz[i] = sz[i] - sz[son[i]];
		return buildT(1, sta[0]);
	}
	
	void change(int x, int y) {
		g[x][1] += y - a[x]; a[x] = y;
		static int bef[2], aft[2];
		for (; x; x = fa[x]) {//因为我们构造出的数深度已经保证,所以可以直接一步一步暴力跳
			bef[0] = max(Sum[x].a[0][0], Sum[x].a[0][1]);
			bef[1] = max(Sum[x].a[1][0], Sum[x].a[1][1]);
			Make_val(x); up(x);//记得在树上传值
			aft[0] = max(Sum[x].a[0][0], Sum[x].a[0][1]);
			aft[1] = max(Sum[x].a[1][0], Sum[x].a[1][1]);
			if (!nrt[x]) {//如果是当前树的根节点就要手动传值了
				g[fa[x]][0] += max(aft[0], aft[1]) - max(bef[0], bef[1]);
				g[fa[x]][1] += aft[0] - bef[0];
			}
		}
	}
}T;

int main() {
//	scanf("%d %d", &n, &m); 
	n = read(); m = read();
//	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 1; i < n; i++) {
//		int x, y; scanf("%d %d", &x, &y); add(x, y); add(y, x);
		int x = read(), y = read(); add(x, y); add(y, x);
	}
	
	dfs0(1, 0);
	dfs1(1, 0);
	one.a[0][0] = 0; one.a[0][1] = -INF;
	one.a[1][0] = -INF; one.a[1][1] = 0;
	T.Val[0] = T.Sum[0] = one;
	root = T.build(1, 0);
	T.Init_nrt();
	
	while (m--) {
//		int x, y; scanf("%d %d", &x, &y); x ^= lstans;
		int x = read() ^ lstans, y = read();
		T.change(x, y); lstans = T.Sum[root].getans();
//		printf("%d\n", lstans);
		write(lstans); putchar('\n');
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值