树链剖分

\(\text{luogu-3384}\)

如题,已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 \(x\)\(y\) 结点最短路径上所有节点的值都加上 \(z\)

  • 2 x y,表示求树从 \(x\)\(y\) 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)

  • 4 x,表示求以 \(x\) 为根节点的子树内所有节点值之和。

\(1\le N \leq {10}^5\)\(1\le M \leq {10}^5\)\(1\le R\le N\)\(1\le P \le 2^{30}\)


重链剖分模板题。

以下部分题解来自于 题解 P3384 - 洛谷专栏,经过了加工完善。

前置知识:lca、树形 dp、dfs 序、链式前向星、线段树。

概况

树链剖分就是对一棵树分成几条链,把树形变为线性,减少处理难度。

需要处理的问题:

  • \(x \to y\) 上所有节点的值都加上 \(z\)
  • \(x \to y\) 上所有节点的值之和。
  • 将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)
  • 求以 \(x\) 为根节点的子树内所有节点值之和。

概念

  • 重儿子:对于每一个非叶子节点,它的儿子中子树节点个数最多的儿子为该节点的重儿子。
  • 轻儿子:对于每一个非叶子节点,它的儿子中的非重儿子即为轻儿子。
  • 叶子节点没有重儿子也没有轻儿子(因为它没有儿子)。
  • 重边:连接任意两个重儿子的边叫做重边。
  • 轻边:剩下的即为轻边。
  • 重链:相邻重边连起来的 连接一条重儿子 的链叫重链。
  • 对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为 \(1\) 的链。
  • 每一条重链以轻儿子为起点。

第一遍 \(\text{dfs}\)

要处理的几件事情:

  • 记录每个点的深度 \(dep_x\)
  • 记录每个点的父亲 \(fa_x\)
  • 记录每个非叶子节点的子树大小(含它自己)。
  • 记录每个非叶子节点的重儿子编号 \(son_x\)
void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

第二遍 \(\text{dfs}\)

要处理的几件事情

  • 标记每个点的新编号 \(dfn_x\)
  • 赋值每个点的初始值到新编号上 \(w_x\)
  • 处理每个点所在链的顶端 \(top_x\)
  • 处理每条链。

先处理重儿子再处理轻儿子

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

处理问题

前面说到第二遍 \(\text{dfs}\) 的顺序是先处理重儿子再处理轻儿子,我们来模拟一下:

  • 因为顺序是先重再轻,所以每一条重链的新编号是连续的。
  • 因为是 \(\text{dfs}\),所以每一个子树的新编号也是连续的。

现在回顾一下我们要处理的问题。

  • 处理任意两点间路径上的点权和。
  • 处理一点及其子树的点权和。
  • 修改任意两点间路径上的点权。
  • 修改一点及其子树的点权。

当我们要处理任意两点间路径时:设所在链顶端的深度更深的那个点为 \(x\) 点。

  • \(res\) 加上 \(x\) 点到 \(top_x\) 这一段区间的点权和。
  • \(x\) 跳到 \(fa_{top_x}\)

不停执行这两个步骤,直到两个点处于一条链上,这时再加上此时两个点的区间和即可。

用线段树处理这部分的操作。每次查询时间复杂度为 \(O(\log^2 n)\)

ll qryrg(ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res = (res + qry(1, dfn[top[x]], dfn[x])) % MOD;
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res = (res + qry(1, dfn[x], dfn[y])) % MOD;
	return res;
}

处理一点及其子树的点权和:实际上 \(x\) 的子树的编号范围就是 \(dfn_x \sim dfn_x + sz_x - 1\)

于是直接线段树区间查询即可。时间复杂度为 \(O(\log n)\)

ll qryson(ll x) { return qry(1, dfn[x], dfn[x] + sz[x] - 1); }

当然,区间修改就和区间查询一样的。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define MAXN 100005
#define ll long long 

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, m, rt, MOD, a[MAXN], son[MAXN], f[MAXN], sz[MAXN], dep[MAXN];
struct node { long long l, r, w, lz; } t[MAXN << 2];
ll cnt, dfn[MAXN], w[MAXN], top[MAXN];
vector<ll> v[MAXN];

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

void build(ll p, ll l, ll r) {
	t[p].l = l, t[p].r = r, t[p].lz = 0;
	if(l == r) { t[p].w = w[l]; return; }
	ll mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	t[p].w = (t[p << 1].w + t[p << 1 | 1].w) % MOD;
	return;
}

void pushdown(ll p) {
	ll lz = t[p].lz;
	(t[p << 1].lz += lz) %= MOD, (t[p << 1 | 1].lz += lz) %= MOD;
	(t[p << 1].w += lz * (t[p << 1].r - t[p << 1].l + 1) % MOD) %= MOD;
	(t[p << 1 | 1].w += lz * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1) % MOD) %= MOD;
	t[p].lz = 0; return;
}

void upd(ll p, ll l, ll r, ll x) {
	if(l <= t[p].l && t[p].r <= r) {
		t[p].w = (t[p].w + x * (t[p].r - t[p].l + 1)) % MOD;
		t[p].lz = (t[p].lz + x) % MOD; return;
	}
	ll mid = (t[p].l + t[p].r) >> 1;
	if(t[p].lz) pushdown(p);
	if(l <= mid) upd(p << 1, l, r, x);
	if(r > mid) upd(p << 1 | 1, l, r, x);
	t[p].w = (t[p << 1].w + t[p << 1 | 1].w) % MOD;
	return;
}

ll qry(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].w % MOD;
	ll mid = (t[p].l + t[p].r) >> 1, res = 0;
	if(t[p].lz) pushdown(p);
	if(l <= mid) res = (res + qry(p << 1, l, r)) % MOD;
	if(r > mid) res = (res + qry(p << 1 | 1, l, r)) % MOD;
	return res;
}

void updrg(ll x, ll y, ll z) {
	z %= MOD;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		upd(1, dfn[top[x]], dfn[x], z);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	upd(1, dfn[x], dfn[y], z);
	return;
}

ll qryrg(ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res = (res + qry(1, dfn[top[x]], dfn[x])) % MOD;
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res = (res + qry(1, dfn[x], dfn[y])) % MOD;
	return res;
}

void updson(ll x, ll z) {
	upd(1, dfn[x], dfn[x] + sz[x] - 1, z);
	return;
}

ll qryson(ll x) { return qry(1, dfn[x], dfn[x] + sz[x] - 1); }

int main() {
	n = read(), m = read(), rt = read(), MOD = read();
	for(int i = 1; i <= n; i ++) a[i] = read() % MOD;
	for(int i = 1; i < n; i ++) {
		ll x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	dfs1(rt, 0), dfs2(rt, rt), build(1, 1, n);
	while(m --) {
		ll op = read(), x, y, z;
		if(op == 1) x = read(), y = read(), z = read(), updrg(x, y, z);
		else if(op == 2) x = read(), y = read(), cout << qryrg(x, y) << "\n";
		else if(op == 3) x = read(), y = read(), updson(x, y);
		else x = read(), cout << qryson(x) << "\n";
	}
	return 0;
}

\(\text{luogu-2590}\)

一棵树上有 \(n\) 个节点,编号分别为 \(1\)\(n\),每个节点都有一个权值 \(w\)

我们将以下面的形式来要求你对这棵树完成一些操作:

I. CHANGE u t : 把结点 \(u\) 的权值改为 \(t\)

II. QMAX u v: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的最大权值。

III. QSUM u v: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的权值和。

注意:从点 \(u\) 到点 \(v\) 的路径上的节点包括 \(u\)\(v\) 本身。

\(1\le n \le 3\times 10^4\)\(0\le q\le 2\times 10^5\)\(-3 \times 10^4 \le w_i \le 3 \times 10^4\)


树剖+线段树。

直接树剖转化为区间上的问题,然后用线段树维护区间信息即可。

单点修改甚至不需要懒标记。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define MAXN 30005
#define INF 0x3f3f3f3f

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, q, a[MAXN], f[MAXN], dep[MAXN], sz[MAXN], son[MAXN];
struct node { ll l, r, x, mx; } t[MAXN << 2]; 
ll dfn[MAXN], w[MAXN], top[MAXN], cnt;
vector<ll> v[MAXN];

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] >= sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

void build(ll p, ll l, ll r) {
	t[p] = {l, r, 0, -INF};
	if(l == r) { t[p].x = t[p].mx = w[l]; return; }
	ll mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	t[p].mx = max(t[p << 1].mx, t[p << 1 | 1].mx);
	return;
}

void upd(ll p, ll x, ll k) {
	if(t[p].l == t[p].r) { t[p].x = k, t[p].mx = k; return; }
	ll mid = (t[p].l + t[p].r) >> 1;
	if(x <= mid) upd(p << 1, x, k);
	else upd(p << 1 | 1, x, k);
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	t[p].mx = max(t[p << 1].mx, t[p << 1 | 1].mx);
	return;
}

ll qmx(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].mx;
	ll mid = (t[p].l + t[p].r) >> 1, res = -INF;
	if(l <= mid) res = max(res, qmx(p << 1, l, r));
	if(r > mid) res = max(res, qmx(p << 1 | 1, l, r));
	return res;
}

ll qsum(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].x;
	ll mid = (t[p].l + t[p].r) >> 1, res = 0;
	if(l <= mid) res += qsum(p << 1, l, r);
	if(r > mid) res += qsum(p << 1 | 1, l, r);
	return res;
}

ll qrymx(ll x, ll y) {
	ll res = -INF;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res = max(res, qmx(1, dfn[top[x]], dfn[x]));
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res = max(res, qmx(1, dfn[x], dfn[y]));
	return res;
}

ll qrysum(ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res += qsum(1, dfn[top[x]], dfn[x]);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res += qsum(1, dfn[x], dfn[y]);
	return res;
}

int main() {
	n = read();
	for(int i = 1; i < n; i ++) {
		ll x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	for(int i = 1; i <= n; i ++) a[i] = read();
	q = read(); dfs1(1, 0), dfs2(1, 1), build(1, 1, n);
	while(q --) {
		string s; cin >> s; ll x, y;
		if(s == "CHANGE") x = read(), y = read(), upd(1, dfn[x], y);
		else if(s == "QMAX") x = read(), y = read(), cout << qrymx(x, y) << "\n";
		else x = read(), y = read(), cout << qrysum(x, y) << "\n";
	}
	return 0;
}

\(\text{luogu-3313}\)

S 国有 \(n\) 个城市,编号从 \(1\)\(n\)。城市间用 \(n-1\) 条双向道路连接,满足从一个城市出发可以到达其它所有城市。每个城市信仰不同的宗教,如飞天面条神教、隐形独角兽教、绝地教都是常见的信仰。

为了方便,我们用不同的正整数代表各种宗教,S 国的居民常常旅行。旅行时他们总会走最短路,并且为了避免麻烦,只在信仰和他们相同的城市留宿。当然旅程的终点也是信仰与他相同的城市。S 国为每个城市标定了不同的旅行评级,旅行者们常会记下途中(包括起点和终点)留宿过的城市的评级总和或最大值。

在 S 国的历史上常会发生以下几种事件:

  • CC x c:城市 \(x\) 的居民全体改信了 \(c\) 教;
  • CW x w:城市 \(x\) 的评级调整为 \(w\)
  • QS x y:一位旅行者从城市 \(x\) 出发,到城市 \(y\),并记下了途中留宿过的城市的评级总和;
  • QM x y:一位旅行者从城市 \(x\) 出发,到城市 \(y\),并记下了途中留宿过的城市的评级最大值。

由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。

\(1 \le n,q \le 10^5\)\(0 \le c_i \le 10^5\)\(0 \le w_i \le \min(10^4, c)\)


可以树剖完用 \(\max c_i\) 棵动态开点线段树维护。

但是显然用分块写更容易一点,时间复杂度 \(O(n \sqrt n \log n)\),常数小。

不考虑树剖常数的话,可以证明最优的块长是 \(\sqrt \frac{n}{\log n}\)

注意:块的个数大约是 \(1300\) 左右,建议开 \(1500\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
#define ll int
#define MAXN 100005
#define INF 0x3f3f3f3f

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, q, B, a[MAXN], b[MAXN], f[MAXN], dep[MAXN], sz[MAXN], son[MAXN];
ll dfn[MAXN], w[MAXN], top[MAXN], cnt, l[MAXN], r[MAXN];
ll bl[MAXN], s[MAXN][1505], mx[MAXN][1505], c[MAXN];
vector<ll> v[MAXN];

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], c[cnt] = b[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

void updb(ll x, ll y) {
	ll k = bl[x], lst = c[x]; c[x] = y;
	s[lst][k] -= w[x], s[y][k] += w[x];
	mx[lst][k] = mx[y][k] = -INF;
	for(int i = l[k]; i <= r[k]; i ++) 
		if(c[i] == lst) mx[lst][k] = max(mx[lst][k], w[i]);
		else if(c[i] == y) mx[y][k] = max(mx[y][k], w[i]);
	return;
}

void upda(ll x, ll y) {
	ll k = bl[x]; mx[c[x]][k] = -INF;
	s[c[x]][k] += y - w[x], w[x] = y;
	for(int i = l[k]; i <= r[k]; i ++)
		if(c[i] == c[x]) mx[c[x]][k] = max(mx[c[x]][k], w[i]);
	return;
}

ll qsum(ll p, ll x, ll y) {
	ll res = 0, pl = bl[x], pr = bl[y];
	if(pl == pr) {
		for(int i = x; i <= y; i ++) if(c[i] == p) res += w[i];
		return res;
	}
	for(int i = x; i <= r[pl]; i ++) if(c[i] == p) res += w[i];
	for(int i = l[pr]; i <= y; i ++) if(c[i] == p) res += w[i];
	for(int i = pl + 1; i < pr; i ++) res += s[p][i];
	return res;
}

ll qmx(ll p, ll x, ll y) {
	ll res = -INF, pl = bl[x], pr = bl[y];
	if(pl == pr) {
		for(int i = x; i <= y; i ++) if(c[i] == p) res = max(res, w[i]);
		return res;
	}
	for(int i = x; i <= r[pl]; i ++) if(c[i] == p) res = max(res, w[i]);
	for(int i = l[pr]; i <= y; i ++) if(c[i] == p) res = max(res, w[i]);
	for(int i = pl + 1; i < pr; i ++) res = max(res, mx[p][i]);
	return res;
}

ll qrysum(ll p, ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res += qsum(p, dfn[top[x]], dfn[x]);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res += qsum(p, dfn[x], dfn[y]);
	return res;
}

ll qrymx(ll p, ll x, ll y) {
	ll res = -INF;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res = max(res, qmx(p, dfn[top[x]], dfn[x]));
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res = max(res, qmx(p, dfn[x], dfn[y]));
	return res;
}

int main() {
	n = read(), q = read(), B = sqrt(1.0 * n / log2(n));
	for(int i = 1; i <= n; i ++) a[i] = read(), b[i] = read();
	for(int i = 1; i < n; i ++) {
		ll x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	dfs1(1, 0), dfs2(1, 1);
	for(int i = 0; i <= n; i ++) l[i] = INF, r[i] = -INF;
	for(int i = 1; i <= n; i ++) {
		ll k = i / B; bl[i] = k;
		l[k] = min(l[k], i), r[k] = max(r[k], i);
		s[c[i]][k] += w[i], mx[c[i]][k] = max(mx[c[i]][k], w[i]);
	}
	while(q --) {
		string s; cin >> s; ll x, y;
		if(s == "CC") x = read(), y = read(), updb(dfn[x], y);
		else if(s == "CW") x = read(), y = read(), upda(dfn[x], y);
		else if(s == "QS") x = read(), y = read(), cout << qrysum(c[dfn[x]], x, y) << "\n";
		else x = read(), y = read(), cout << qrymx(c[dfn[x]], x, y) << "\n";
	}
	return 0;
}

\(\text{hdu-3966}\)

给定一棵 \(n\) 个节点的树,点有点权,有以下三种操作:

  • I x y k\(x \to y\) 路径上的所有节点的点权加 \(k\)
  • D x y k\(x \to y\) 路径上的所有节点的点权减 \(k\)
  • Q x 查询 \(x\) 节点的点权。

\(1 \le n \le 5 \times 10^5\)\(m=n-1\)\(1 \le q \le 10^5\)\(0 \le a_i,k \le 1000\)


直接树剖,然后用线段树维护就好了,模板题。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define MAXN 50005

ll n, m, q, a[MAXN], f[MAXN], dep[MAXN], sz[MAXN], son[MAXN];
struct node { ll l, r, x, lz; } t[MAXN << 2];
ll dfn[MAXN], w[MAXN], top[MAXN], cnt;
vector<ll> v[MAXN];

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

void build(ll p, ll l, ll r) {
	t[p] = {l, r, 0, 0};
	if(l == r) { t[p].x = w[l]; return; }
	ll mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	return;
}

void pushdown(ll p) {
	ll lz = t[p].lz;
	t[p << 1].x += lz * (t[p << 1].r - t[p << 1].l + 1);
	t[p << 1 | 1].x += lz * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1);
	t[p << 1].lz += lz, t[p << 1 | 1].lz += lz, t[p].lz = 0;
	return;
}

void update(ll p, ll l, ll r, ll x) {
	if(l <= t[p].l && t[p].r <= r) {
		t[p].x += x * (t[p].r - t[p].l + 1);
		t[p].lz += x; return;
	}
	ll mid = (t[p].l + t[p].r) >> 1;
	if(t[p].lz) pushdown(p); 
	if(l <= mid) update(p << 1, l, r, x);
	if(r > mid) update(p << 1 | 1, l, r, x);
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	return;
}

void upd(ll x, ll y, ll k) {
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		update(1, dfn[top[x]], dfn[x], k);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	update(1, dfn[x], dfn[y], k);
	return;
}

ll qry(ll p, ll x) {
	if(t[p].l == t[p].r) return t[p].x;
	ll mid = (t[p].l + t[p].r) >> 1;
	if(t[p].lz) pushdown(p);
	if(x <= mid) return qry(p << 1, x);
	return qry(p << 1 | 1, x);
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	while(cin >> n >> m >> q) {
		for(int i = 1; i <= n; i ++) cin >> a[i];
		for(int i = 1; i <= n; i ++) v[i].clear();
		cnt = 0;
		for(int i = 1; i <= m; i ++) {
			ll x, y; cin >> x >> y;
			v[x].push_back(y), v[y].push_back(x);
		}
		dfs1(1, 0), dfs2(1, 1), build(1, 1, n);
		while(q --) {
			char c; cin >> c; ll x, y, k;
			if(c == 'I') cin >> x >> y >> k, upd(x, y, k);
			else if(c == 'D') cin >> x >> y >> k, upd(x, y, -k);
			else cin >> x, cout << qry(1, dfn[x]) << "\n";
		}
	}
	return 0;
}

\(\text{LightOJ-1348}\)

给定一棵 \(n\) 个节点的树,点有点权,有以下三种操作:

  • 0 x y 查询 \(x \to y\) 路径上的所有点权和。
  • 1 x y\(x\) 节点的点权变为 \(y\)

\(1 \le T \le 5\)\(2 \le n \le 3 \times 10^4\)\(1 \le q \le 10^5\)


直接树剖,然后用线段树维护就好了,模板题。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll int
#define MAXN 30005
#define INF 0x3f3f3f3f

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll T, n, q, a[MAXN], f[MAXN], dep[MAXN], sz[MAXN], son[MAXN];
ll dfn[MAXN], w[MAXN], top[MAXN], cnt, tot;
struct node { ll l, r, x; } t[MAXN << 2]; 
vector<ll> v[MAXN];

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] >= sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

void build(ll p, ll l, ll r) {
	t[p] = {l, r, 0};
	if(l == r) { t[p].x = w[l]; return; }
	ll mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	return;
}

void upd(ll p, ll x, ll k) {
	if(t[p].l == t[p].r) { t[p].x = k; return; }
	ll mid = (t[p].l + t[p].r) >> 1;
	if(x <= mid) upd(p << 1, x, k);
	else upd(p << 1 | 1, x, k);
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	return;
}

ll qsum(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].x;
	ll mid = (t[p].l + t[p].r) >> 1, res = 0;
	if(l <= mid) res += qsum(p << 1, l, r);
	if(r > mid) res += qsum(p << 1 | 1, l, r);
	return res;
}

ll qrysum(ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res += qsum(1, dfn[top[x]], dfn[x]);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res += qsum(1, dfn[x], dfn[y]);
	return res;
}

int main() {
	T = read();
	while(T --) {
		n = read(), cnt = 0;
		for(int i = 1; i <= n; i ++) a[i] = read();
		for(int i = 1; i <= n; i ++) v[i].clear();
		for(int i = 1; i < n; i ++) {
			ll x = read() + 1, y = read() + 1;
			v[x].push_back(y), v[y].push_back(x);
		}
		q = read(); dfs1(1, 0), dfs2(1, 1), build(1, 1, n);
		cout << "Case " << (++ tot) << ":\n";
		while(q --) {
			ll x = 1, y = 1;
			if(read()) x += read(), y = read(), upd(1, dfn[x], y);
			else x += read(), y += read(), cout << qrysum(x, y) << "\n";
		}
	} 
	return 0;
}

\(\text{luogu-1505}\)

给定一棵 \(n\) 个节点的树,边带权,编号 \(0 \sim n-1\),需要支持五种操作:

  • C i w 将输入的第 \(i\) 条边权值改为 \(w\)
  • N u v\(u,v\) 节点之间的边权都变为相反数;
  • SUM u v 询问 \(u,v\) 节点之间边权和;
  • MAX u v 询问 \(u,v\) 节点之间边权最大值;
  • MIN u v 询问 \(u,v\) 节点之间边权最小值。

保证任意时刻所有边的权值都在 \([-1000,1000]\) 内。

\(1\le n,m \le 2\times 10^5\)


首先树剖,然后考虑怎么用线段树维护。

用线段树记录区间和、区间最大值、区间最小值以及区间反转的懒标记。

区间取相反数时,区间和变为相反数,最大值变为最小值的相反数,最小值变为最大值的相反数。

然后就做完了。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define MAXN 200005
#define pii pair<ll, ll>
#define fi first
#define se second
#define INF 0x3f3f3f3f

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, m, a[MAXN], b[MAXN], p[MAXN], f[MAXN], dep[MAXN], sz[MAXN];
struct node { ll l, r, x, mx, mn, f; } t[MAXN << 2];
ll son[MAXN], dfn[MAXN], cnt, w[MAXN], top[MAXN];
vector<pii > v[MAXN];

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto it : v[x]) if(it.fi != fa) {
		ll y = it.fi, t = it.se;
		p[t] = y, dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = b[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto it : v[x]) if(it.fi != son[x] 
		&& it.fi != f[x]) dfs2(it.fi, it.fi);
	return;
}

void pushup(ll p) {
	t[p].x = t[p << 1].x + t[p << 1 | 1].x;
	t[p].mx = max(t[p << 1].mx, t[p << 1 | 1].mx);
	t[p].mn = min(t[p << 1].mn, t[p << 1 | 1].mn);
	return;
}

void pushdown(ll p) {
	t[p << 1].x *= -1, t[p << 1 | 1].x *= -1;
	ll mx1 = t[p << 1].mx, mx2 = t[p << 1 | 1].mx;
	ll mn1 = t[p << 1].mn, mn2 = t[p << 1 | 1].mn;
	t[p << 1].mx = -mn1, t[p << 1 | 1].mx = -mn2;
	t[p << 1].mn = -mx1, t[p << 1 | 1].mn = -mx2;
	t[p << 1].f ^= 1, t[p << 1 | 1].f ^= 1, t[p].f = 0;
	return;
}

void build(ll p, ll l, ll r) {
	t[p] = {l, r, 0, -INF, INF, 0};
	if(l == r) {
		t[p].x = t[p].mx = t[p].mn = w[l]; 
		return;
	}
	ll mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	pushup(p); return;
}

void upd(ll p, ll x, ll k) {
	if(t[p].l == t[p].r) { 
		t[p].x = t[p].mx = t[p].mn = k; 
		return;
	}
	ll mid = (t[p].l + t[p].r) >> 1;
	if(t[p].f) pushdown(p);
	if(x <= mid) upd(p << 1, x, k);
	else upd(p << 1 | 1, x, k);
	pushup(p); return;
}

void upd1(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) {
		t[p].x *= -1; ll mx = t[p].mx, mn = t[p].mn;
		t[p].mx = -mn, t[p].mn = -mx, t[p].f ^= 1; 
		return;
	}
	ll mid = (t[p].l + t[p].r) >> 1;
	if(t[p].f) pushdown(p);
	if(l <= mid) upd1(p << 1, l, r);
	if(r > mid) upd1(p << 1 | 1, l, r);
	pushup(p); return;
}

void upd2(ll x, ll y) {
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		upd1(1, dfn[top[x]], dfn[x]);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	upd1(1, dfn[x] + 1, dfn[y]);
	return;
}

ll qsum(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].x;
	ll mid = (t[p].l + t[p].r) >> 1, res = 0;
	if(t[p].f) pushdown(p);
	if(l <= mid) res += qsum(p << 1, l, r);
	if(r > mid) res += qsum(p << 1 | 1, l, r);
	return res;
}

ll qmx(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].mx;
	ll mid = (t[p].l + t[p].r) >> 1, res = -INF;
	if(t[p].f) pushdown(p);
	if(l <= mid) res = max(res, qmx(p << 1, l, r));
	if(r > mid) res = max(res, qmx(p << 1 | 1, l, r));
	return res;
}

ll qmn(ll p, ll l, ll r) {
	if(l <= t[p].l && t[p].r <= r) return t[p].mn;
	ll mid = (t[p].l + t[p].r) >> 1, res = INF;
	if(t[p].f) pushdown(p);
	if(l <= mid) res = min(res, qmn(p << 1, l, r));
	if(r > mid) res = min(res, qmn(p << 1 | 1, l, r));
	return res;
}

ll qrysum(ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res += qsum(1, dfn[top[x]], dfn[x]);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res += qsum(1, dfn[x] + 1, dfn[y]);
	return res;
}

ll qrymx(ll x, ll y) {
	ll res = -INF;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res = max(res, qmx(1, dfn[top[x]], dfn[x]));
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res = max(res, qmx(1, dfn[x] + 1, dfn[y]));
	return res;
}

ll qrymn(ll x, ll y) {
	ll res = INF;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res = min(res, qmn(1, dfn[top[x]], dfn[x]));
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	res = min(res, qmn(1, dfn[x] + 1, dfn[y]));
	return res;
}

int main() {
	n = read();
	for(int i = 1; i < n; i ++) {
		ll x = read() + 1, y = read() + 1, t = read(); a[i] = t;
		v[x].push_back({y, i}), v[y].push_back({x, i});
	}
	m = read(), dfs1(1, 0);
	for(int i = 1; i < n; i ++) b[p[i]] = a[i];
	dfs2(1, 1), build(1, 1, n);
	while(m --) {
		string s; cin >> s; ll x = 1, y = 1;
		if(s == "C") x = read(), y = read(), upd(1, dfn[p[x]], y);
		else if(s == "N") x += read(), y += read(), upd2(x, y);
		else if(s == "SUM") x += read(), y += read(), cout << qrysum(x, y) << "\n";
		else if(s == "MAX") x += read(), y += read(), cout << qrymx(x, y) << "\n";
		else x += read(), y += read(), cout << qrymn(x, y) << "\n"; 
	}
	return 0;
}

\(\text{luogu-2386}\)

给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:

  1. 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\)\(b\))都染成颜色 \(c\)
  2. 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:112221

\(1 \leq n, m \leq 10^5\)\(1 \leq w_i, c \leq 10^9\)\(1 \leq a, b, u, v \leq n\)


首先树剖,然后用 ODT 维护即可。树剖分成若干段之后,注意去重。

#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
using namespace std;
#define ll long long
#define MAXN 100005

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, q, a[MAXN], f[MAXN], dep[MAXN], sz[MAXN], son[MAXN];
ll dfn[MAXN], cnt, top[MAXN], w[MAXN];
struct node { ll l, r, x; };
vector<ll> v[MAXN];
set<node> s;

bool operator < (const node &x, const node &y) {
	return x.l < y.l;
}

void dfs1(ll x, ll fa) {
	f[x] = fa, dep[x] = dep[fa] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != fa) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != f[x]) dfs2(y, y);
	return;
}

auto split(ll x) {
	auto p = s.lower_bound({x, 0, 0});
	if(p != s.end() && p->l == x) return p;
	p --; ll l = p->l, r = p->r, w = p->x;
	s.erase(p), s.insert({l, x - 1, w});
	return s.insert({x, r, w}).first;
}

void assign(ll l, ll r, ll x) {
	auto pr = split(r + 1), pl = split(l);
	s.erase(pl, pr), s.insert({l, r, x});
	return;
}

ll ask(ll l, ll r) {
	auto pr = split(r + 1), pl = split(l);
	ll lst = -1, res = 0;
	while(pl != pr) {
		if(pl->x != lst) res ++, lst = pl->x;
		pl ++;
	}
	return res;
}

ll cg(ll x) {
	auto p = s.lower_bound({x, 0, 0});
	if(p != s.end() && p->l == x) return p->x;
	p --; return p->x;
}

void upd(ll x, ll y, ll z) {
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		assign(dfn[top[x]], dfn[x], z);
		x = f[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	assign(dfn[x], dfn[y], z);
	return;
}

ll qry(ll x, ll y) {
	ll res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		res += ask(dfn[top[x]], dfn[x]);
		ll t = f[top[x]];
		if(cg(dfn[top[x]]) == cg(dfn[t])) res --;
		x = t;
	}
	if(dep[x] > dep[y]) swap(x, y);
	res += ask(dfn[x], dfn[y]);
	return res;
} 

int main() {
	n = read(), q = read();
	for(int i = 1; i <= n; i ++) a[i] = read();
	for(int i = 1; i < n; i ++) {
		ll x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	dfs1(1, 0), dfs2(1, 1);
	for(int i = 1; i <= n; i ++) s.insert({i, i, w[i]});
	s.insert({n + 1, n + 1, -1});
	while(q --) {
		char c; cin >> c; ll x = read(), y = read(), z;
		if(c == 'C') z = read(), upd(x, y, z);
		else cout << qry(x, y) << "\n";
	}
	return 0;
}

\(\text{luogu-3258}\)

松鼠的新家是一棵树,前几天刚刚装修了新家,新家有 \(n\) 个房间,并且有 \(n-1\) 根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。

松鼠想邀请小熊前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去 \(a_1\),再去 \(a_2\),……,最后到 \(a_n\),去参观新家。可是这样会导致重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。

维尼是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。

因为松鼠参观指南上的最后一个房间 \(a_n\) 是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

\(2 \le n \le 3 \times 10^5\)\(1 \le a_i \le n\)


树剖专题里怎么混了道绿题。

不过确实可以用树剖做,就是直接剖完之后用线段树维护区间加即可。

但是显然有更简单的做法,写个 lca,树上差分维护就好了,注意别算重了。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define MAXN 300005

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

ll n, a[MAXN], b[MAXN], dep[MAXN], f[MAXN][20], lg[MAXN];
vector<ll> v[MAXN];

void dfs(ll x, ll fa) {
	f[x][0] = fa, dep[x] = dep[fa] + 1;
	for(int i = 1; i <= lg[dep[x]]; i ++) 
		f[x][i] = f[f[x][i - 1]][i - 1];
	for(auto y : v[x]) if(y != fa) dfs(y, x);
	return;
}

ll lca(ll x, ll y) {
	if(dep[x] < dep[y]) swap(x, y);
	while(dep[x] > dep[y]) 
		x = f[x][lg[dep[x] - dep[y]] - 1];
	if(x == y) return x;
	for(int i = lg[dep[x]] - 1; i >= 0; i --)
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}

void dfs2(ll x) {
	for(auto y : v[x]) if(y != f[x][0]) dfs2(y), b[x] += b[y];
	return;
}

int main() {
	n = read();
	for(int i = 1; i <= n; i ++) 
		a[i] = read(), lg[i] = lg[i >> 1] + 1;
	for(int i = 1; i < n; i ++) {
		ll x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	dfs(1, 0);
	for(int i = 1; i < n; i ++) {
		ll x = a[i], y = a[i + 1], z = lca(x, y);
		b[x] ++, b[y] ++, b[z] --, b[f[z][0]] --;
	}
	dfs2(1);
	for(int i = 2; i <= n; i ++) b[a[i]] --;
	for(int i = 1; i <= n; i ++) cout << b[i] << "\n";
	return 0;
}

\(\text{luogu-5354}\)

给你一个有 \(n\) 个点的树,每个点的包括一个位运算 \(opt\) 和一个权值 \(x\),位运算有&|^ 三种,分别用 \(1,2,3\) 表示。

每次询问包含三个整数 \(x,y,z\),初始选定一个数 \(v\)。然后 \(v\) 依次经过从 \(x\)\(y\) 的所有节点,每经过一个点 \(i\)\(v\) 就变成 \(v\ opt_i\ x_i\),所以他想问你,最后到 \(y\) 时,希望得到的值尽可能大,求最大值。给定的初始值 \(v\) 必须是在 \([0,z]\) 之间。

每次修改包含三个整数 \(x,y,z\),意思是把 \(x\) 点的操作修改为 \(y\),数值改为 \(z\)

\(0\leq n,m \leq 10^5\)\(0\leq k\leq 64\)


以下部分题解来自于 题解 P3613 【睡觉困难综合征】 - 洛谷专栏

首先发现每一位不会互相影响,可以把每一位分开考虑,然后用树链剖分或者 LCT 维护这个树。

修改直接修改,询问的时候算出来每一位填 \(01\) 经过这条链的变换之后得到的值。

考虑贪心,从高往低。

如果这一位填 \(0\) 可以得到 \(1\),那么填 \(0\) 一定是最优的。否则如果可以填 \(1\),就把这一位填为 \(1\)

复杂度是 \(O(n k \log^2 n)\) 或者 \(O(nk \log n)\),只能通过 \(50\%\) 的数据。

发现可以并行计算这 \(k\) 位,复杂度降为 \(n \log^2 n\) 的树链剖分或者 \(n \log n\) 的 LCT。

这个题没有卡常,合并信息不是 \(O(1)\) 的算法没有通过是很正常的吧。

最优复杂度是 \(\log^2 n\),不过期望下大概是 \(\log n \log\log n\) 的感觉

这个题的最优复杂度为 \(O(n + q(\log n + k ))\),至少目前来说是这样的。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define ull unsigned long long
#define MAXN 100005

ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { ull l0, l1, r0, r1; } a[MAXN], w[MAXN], t[MAXN << 2];
ll n, m, k, fa[MAXN], dep[MAXN], sz[MAXN], son[MAXN];
ll dfn[MAXN], cnt, top[MAXN];
vector<ll> v[MAXN];

ull f(ull x, ull y, ull z) { return (z & x) | (y & ~x); }

node operator + (const node &x, const node &y) {
	return {f(x.l0, y.l0, y.l1), f(x.l1, y.l0, y.l1),
			f(y.r0, x.r0, x.r1), f(y.r1, x.r0, x.r1)};
}

node trans(ull x, ull y) {
	if(x == 1) return {0, y, 0, y};
	else if(x == 2) return {y, -1ull, y, -1ull};
	return {y, ~y, y, ~y};
}

void dfs1(ll x, ll ff) {
	fa[x] = ff, dep[x] = dep[ff] + 1;
	sz[x] = 1, son[x] = 0;
	for(auto y : v[x]) if(y != ff) {
		dfs1(y, x), sz[x] += sz[y];
		if(sz[y] > sz[son[x]]) son[x] = y;
	}
	return;
}

void dfs2(ll x, ll t) {
	dfn[x] = (++ cnt), w[cnt] = a[x], top[x] = t;
	if(!son[x]) return; dfs2(son[x], t);
	for(auto y : v[x]) if(y != son[x] && y != fa[x]) dfs2(y, y);
	return;
}

void build(ll p, ll l, ll r) {
	if(l == r) { t[p] = w[l]; return; }
	ll mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	t[p] = t[p << 1] + t[p << 1 | 1];
	return;
}

void upd(ll p, ll x, node y, ll l, ll r) {
	if(l == r) { t[p] = y; return; }
	ll mid = (l + r) >> 1;
	if(x <= mid) upd(p << 1, x, y, l, mid);
	else upd(p << 1 | 1, x, y, mid + 1, r);
	t[p] = t[p << 1] + t[p << 1 | 1];
	return;
}

node qry(ll p, ll L, ll R, ll l, ll r) {
	if(L <= l && r <= R) return t[p];
	ll mid = (l + r) >> 1;
	node res; res.l0 = res.r0 = 0, res.l1 = res.r1 = -1ull;
	if(L <= mid) res = res + qry(p << 1, L, R, l, mid);
	if(R > mid) res = res + qry(p << 1 | 1, L, R, mid + 1, r);
	return res;
}

ull query(ull x, ull y, ull z) {
	ull res = 0; node X, Y;
	X.l0 = X.r0 = 0, X.l1 = X.r1 = -1ull;
	Y.l0 = Y.r0 = 0, Y.l1 = Y.r1 = -1ull;
	while(top[x] != top[y]) 
		if(dfn[top[x]] > dfn[top[y]])
			X = qry(1, dfn[top[x]], dfn[x], 1, n) + X, x = fa[top[x]];
		else Y = qry(1, dfn[top[y]], dfn[y], 1, n) + Y, y = fa[top[y]];
	if(dfn[x] > dfn[y]) X = qry(1, dfn[y], dfn[x], 1, n) + X;
	else Y = qry(1, dfn[x], dfn[y], 1, n) + Y;
	Y = (node){X.r0, X.r1, 0, 0} + Y;
	for(int i = k - 1; i >= 0; i --) {
		ull t = (1ull << i);
		if(res + t <= z && (Y.l0 & t) < (Y.l1 & t)) res |= t;
	}
	return f(res, Y.l0, Y.l1);
}

int main() {
	n = read(), m = read(), k = read();
	for(int i = 1; i <= n; i ++) {
		ull x = read(), y = read();
		a[i] = trans(x, y);
	}
	for(int i = 1; i < n; i ++) {
		ll x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	dfs1(1, 0), dfs2(1, 1), build(1, 1, n);
	while(m --) {
		ull op = read(), x = read(), y = read(), z = read();
		if(op == 1) cout << query(x, y, z) << "\n";
		else upd(1, dfn[x], trans(y, z), 1, n);
	}
	return 0;
}
posted @ 2026-03-17 21:31  So_noSlack  阅读(9)  评论(0)    收藏  举报