树链剖分

定义

是指将一棵树划分成若干条链。常见的剖分方式有长链剖分、重链剖分、实链剖分。

重链剖分

从轻重儿子的角度将树划分成多条“重链”,进而将重链转换成区间问题解决。

步骤

  1. dfs 预处理一棵树的深度、父节点、子树大小、重儿子(\(dep, fa, sz, son\))。
  2. 对于每个轻儿子,再次 dfs,维护每个点的 dfs 序(\(dfn_x\))。递归时,优先去一个点的重儿子。重儿子继承父节点的链头信息(\(top_x\)),轻儿子自己就是链头。
  3. 对连续的 dfs 序维护数据结构,维护相应信息即可。

性质

  1. 重儿子不一定唯一,但确定重儿子后重链剖分唯一。
  2. 一条重链总是从轻儿子开始,其余点为重儿子。
  3. 一条重链的 dfs 序连续。
  4. 一棵子树的 dfs 序连续。
  5. 任意点 \(x\) 到其链头 \(top_x\) 的 dfs 序连续,且深度小的点 dfs 序小。
  6. 从任意点 \(x\) 出发到根节点,中间经过的不同的重链至多 \(\log n\) 条(切换重链子树大小起码翻倍)。
  7. 树上任意一条路径 \((x, y)\) 总是可以由不超过 \(\log n\) 段重链的子链拼接而成。

例题

P3384 【模板】重链剖分/树链剖分

模板。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 1e5 + 5;
int n, Q, u, v, w, op, mod, root, a[N], id[N];
vector<int> g[N];

namespace Segment_Tree {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson ls(p), L, mid
	#define rson rs(p), mid + 1, R
	
	int sum[N << 2], pls[N << 2];
	
	inline int ls(int p) {
		return p << 1;
	}
	
	inline int rs(int p) {
		return p << 1 | 1;
	}
	
	inline void psup(int p) {
		sum[p] = (sum[ls(p)] + sum[rs(p)]) % mod;
		
		return ;
	}
	
	inline void build(int p = 1, int L = 1, int R = n) {
		if(L == R) {
			sum[p] = a[id[L]] % mod;
			
			return ;
		}
		
		build(lson), build(rson), psup(p);
		
		return ;
	}
	
	inline void work(int p, int L, int R, int k) {
		pls[p] = (pls[p] + k) % mod;
		sum[p] = (sum[p] + (R - L + 1) * k % mod) % mod;
		
		return ;
	}
	
	inline void psd(int p, int L, int R) {
		if(! pls[p]) return ;
		
		work(lson, pls[p]), work(rson, pls[p]);
		pls[p] = 0;
		
		return ;
	}
	
	inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) {
			work(son, k);
			
			return ;
		}
		
		psd(son);
		
		if(l <= mid) add(l, r, k, lson);
		if(r > mid) add(l, r, k, rson);
		
		psup(p);
		
		return ;
	}
	
	inline int query(int l, int r, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) return sum[p];
		
		int res = 0;
		
		psd(son);
		
		if(l <= mid) res = (res + query(l, r, lson)) % mod;
		if(r > mid) res = (res + query(l, r, rson)) % mod;
		
		return res;
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson
}

using namespace Segment_Tree;

namespace Graph {
	int times, sz[N], fa[N], dep[N], son[N], dfn[N], top[N];
	
	inline void dfs(int x, int last) {
		sz[x] = 1;
		
		for(auto u : g[x])
			if(u != last) {
				fa[u] = x;
				dep[u] = dep[x] + 1;
				
				dfs(u, x);
				
				sz[x] += sz[u];
				if(sz[u] > sz[son[x]]) son[x] = u;
			}
		
		return ;
	}
	
	inline void DFS(int x) {
		dfn[x] = ++ times;
		id[dfn[x]] = x;
		
		if(son[x]) {
			top[son[x]] = top[x];
			
			DFS(son[x]);
		}
		
		for(auto u : g[x])
			if(! top[u]) {
				top[u] = u;
				
				DFS(u);
			}
		
		return ;
	}
	
	inline void Add(int x, int y, int k) {
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			add(dfn[top[x]], dfn[x], k);
			
			x = fa[top[x]];
		}
		
		if(dfn[x] > dfn[y]) swap(x, y);
		
		add(dfn[x], dfn[y], k);
		
		return ;
	}
	
	inline int Query(int x, int y) {
		int res = 0;
		
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			res = (res + query(dfn[top[x]], dfn[x])) % mod;
			
			x = fa[top[x]];
		}
		
		if(dfn[x] > dfn[y]) swap(x, y);
		
		res = (res + query(dfn[x], dfn[y])) % mod;
		
		return res % mod;
	}
}

using namespace Graph;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q >> root >> mod;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	for(int i = 1 ; i < n ; ++ i) {
		cin >> u >> v;
		
		g[u].pb(v),	g[v].pb(u);
	}
	
	dfs(root, -1);
	top[root] = root;
	DFS(root);
	build();
	
	while(Q --) {
		cin >> op >> u;
		
		if(op == 1) {
			cin >> v >> w;
			
			Add(u, v, w);
		}
		else if(op == 2) {
			cin >> v;
			
			cout << Query(u, v) << '\n';
		}
		else if(op == 3) {
			cin >> w;
			
			add(dfn[u], dfn[u] + sz[u] - 1, w);
		}
		else cout << query(dfn[u], dfn[u] + sz[u] - 1) << '\n';
	}
	
	return 0;
}

P1505 [国家集训队] 旅游

路径操作,考虑将路径挂在深度较大的那个点。

需要注意的是,路径操作时当两个点在同一条重链上时,深度较浅的点不计入统计。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 2e5 + 5;
int n, Q, x, y, z, a[N], u[N], v[N], w[N], id[N];
string op;
vector<int> g[N];

namespace Segment_Tree {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson ls(p), L, mid
	#define rson rs(p), mid + 1, R
	
	int mx[N << 2], mn[N << 2], sum[N << 2], mul[N << 2];
	
	inline int ls(int p) {
		return p << 1;
	}
	
	inline int rs(int p) {
		return p << 1 | 1;
	}
	
	inline void psup(int p) {
		mx[p] = max(mx[ls(p)], mx[rs(p)]);
		mn[p] = min(mn[ls(p)], mn[rs(p)]);
		sum[p] = sum[ls(p)] + sum[rs(p)];
		
		return ;
	}
	
	inline void build(int p = 1, int L = 1, int R = n) {
		mx[p] = -1e18;
		mn[p] = 1e18;
		mul[p] = 1;
		
		if(L == R) {
			mx[p] = mn[p] = sum[p] = a[id[L]];
			
			return ;
		}
		
		build(lson), build(rson), psup(p);
		
		return ;
	}
	
	inline void work(int p, int k) {
		mx[p] *= k;
		mn[p] *= k;
		mul[p] *= k;
		sum[p] *= k;
		swap(mx[p], mn[p]);
		
		return ;
	}
	
	inline void psd(int p, int L, int R) {
		if(mul[p] == 1) return ;
		
		work(ls(p), mul[p]), work(rs(p), mul[p]);
		mul[p] = 1;
		
		return ;
	}
	
	inline void modify(int x, int k, int p = 1, int L = 1, int R = n) {
		if(L == R) {
			mx[p] = mn[p] = sum[p] = k;
			
			return ;
		}
		
		psd(son);
		
		if(x <= mid) modify(x, k, lson);
		else modify(x, k, rson);
		
		psup(p);
		
		return ;
	}
	
	inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) {
			work(p, k);
			
			return ;
		}
		
		psd(son);
		
		if(l <= mid) add(l, r, k, lson);
		if(r > mid) add(l, r, k, rson);
		
		psup(p);
		
		return ;
	}
	
	inline int querysum(int l, int r, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) return sum[p];
		
		int res = 0;
		
		psd(son);
		
		if(l <= mid) res += querysum(l, r, lson);
		if(r > mid) res += querysum(l, r, rson);
		
		return res;
	}
	
	inline int querymax(int l, int r, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) return mx[p];
		
		int res = -1e18;
		
		psd(son);
		
		if(l <= mid) res = max(res, querymax(l, r, lson));
		if(r > mid) res = max(res, querymax(l, r, rson));
		
		return res; 
	}
	
	inline int querymin(int l, int r, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) return mn[p];
		
		int res = 1e18;
		
		psd(son);
		
		if(l <= mid) res = min(res, querymin(l, r, lson));
		if(r > mid) res = min(res, querymin(l, r, rson));
		
		return res;
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson
}

using namespace Segment_Tree;

namespace Graph {
	int times, sz[N], fa[N], dep[N], son[N], dfn[N], top[N];
	
	inline void dfs(int x, int last) {
		sz[x] = 1;
		
		for(auto u : g[x])
			if(u != last) {
				fa[u] = x;
				dep[u] = dep[x] + 1;
				
				dfs(u, x);
				
				sz[x] += sz[u];
				if(sz[u] > sz[son[x]]) son[x] = u;
			}
		
		return ;
	}
	
	inline void DFS(int x) {
		dfn[x] = ++ times;
		id[dfn[x]] = x;
		
		if(son[x]) {
			top[son[x]] = top[x];
			
			DFS(son[x]);
		}
		
		for(auto u : g[x])
			if(! top[u]) {
				top[u] = u;
				
				DFS(u);
			}
		
		return ;
	}
	
	inline void Add(int x, int y, int k) {
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			add(dfn[top[x]], dfn[x], k);
			
			x = fa[top[x]];
		}
		
		if(dfn[x] > dfn[y]) swap(x, y);
		
		add(dfn[x] + 1, dfn[y], k);
		
		return ;
	}
	
	inline int Querysum(int x, int y) {
		int res = 0;
		
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			res += querysum(dfn[top[x]], dfn[x]);
			
			x = fa[top[x]];
		}
		
		if(dfn[x] > dfn[y]) swap(x, y);
		
		res += querysum(dfn[x] + 1, dfn[y]);
		
		return res;
	}
	
	inline int Querymax(int x, int y) {
		int res = -1e18;
		
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			res = max(res, querymax(dfn[top[x]], dfn[x]));
			
			x = fa[top[x]];
		}
		
		if(dep[x] > dep[y]) swap(x, y);
		
		res = max(res, querymax(dfn[x] + 1, dfn[y]));
		
		return res;
	}
	
	inline int Querymin(int x, int y) {
		int res = 1e18;
		
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			res = min(res, querymin(dfn[top[x]], dfn[x]));
			
			x = fa[top[x]];
		}
		
		if(dep[x] > dep[y]) swap(x, y);
		
		res = min(res, querymin(dfn[x] + 1, dfn[y]));
		
		return res;
	}
}

using namespace Graph;

signed main() {
//	freopen("P1505_1.in", "r", stdin);
//	freopen("asd.out", "w", stdout);
	
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n ;
	for(int i = 1 ; i < n ; ++ i) {
		cin >> u[i] >> v[i] >> w[i];
		
		++ u[i], ++ v[i];
		
		g[u[i]].pb(v[i]),	g[v[i]].pb(u[i]);
	}
	
	dfs(1, -1);
	top[1] = 1;
	DFS(1);
	
	for(int i = 1 ; i < n ; ++ i) {
		if(dep[u[i]] < dep[v[i]])	swap(u[i], v[i]);
		
		a[u[i]] = w[i];
	}
	
	build();
	
	cin >> Q;
	
	while(Q --) {
		cin >> op;
		
		if(op == "C") {
			cin >> x >> z;
			
			modify(dfn[u[x]], z);
		}
		else if(op == "N") {
			cin >> x >> y;
			
			++ x, ++ y; 
			
			Add(x, y, -1);
		}
		else if(op == "SUM") {
			cin >> x >> y;
			
			++ x, ++ y;
			
			cout << Querysum(x, y) << '\n';
		}
		else if(op == "MAX") {
			cin >> x >> y;
			
			++ x, ++ y;
			
			cout << Querymax(x, y) << '\n';
		}
		else {
			cin >> x >> y;
			
			++ x, ++ y;
			
			cout << Querymin(x, y) << '\n';
		}
	}
	
	return 0;
}

P2486 [SDOI2011] 染色

前情提要:简化版(序列)

线段树维护半群即可。

  1. 将区间问题搬到树上,则可以重剖区间。
  2. 树上统计时换重链时,两条链的交汇处是否颜色一致。

代码:

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 1e5 + 5;
int n, Q, u, v, w, a[N];
char op;

namespace Graph {
	int times, id[N], sz[N], fa[N], dep[N], son[N], top[N], dfn[N];
	vector<int> g[N];
	
	inline void dfs1(int x, int last) {
		sz[x] = 1;
		
		for(auto u : g[x])
			if(u != last) {
				fa[u] = x;
				dep[u] = dep[x] + 1;
				
				dfs1(u, x);
				
				sz[x] += sz[u];
				if(sz[son[x]] < sz[u]) son[x] = u;
			}
		
		return ;
	}
	
	inline void dfs2(int x) {
		dfn[x] = ++ times;
		id[dfn[x]] = x;
		
		if(son[x]) {
			top[son[x]] = top[x];
			
			dfs2(son[x]);
		}
		
		for(auto u : g[x])
			if(! top[u]) {
				top[u] = u;
				
				dfs2(u);
			}
			
		return ;
	}
}

using namespace Graph;

namespace Segment_Tree {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson ls(p), L, mid
	#define rson rs(p), mid + 1, R
	
	struct Node {
		int lc, rc, res, cov;
	} t[N << 2];
	
	inline int ls(int p) {
		return p << 1;
	}
	
	inline int rs(int p) {
		return p << 1 | 1;
	}
	
	inline void psup(int p) {
		t[p].lc = t[ls(p)].lc;
		t[p].rc = t[rs(p)].rc;
		t[p].res = t[ls(p)].res + t[rs(p)].res;
		
		if(t[ls(p)].rc == t[rs(p)].lc) -- t[p].res;
		
		return ;
	}
	
	inline void build(int p = 1, int L = 1, int R = n) {
		if(L == R) {
			t[p].res = 1;
			t[p].lc = t[p].rc = a[id[L]];
			
			return ;
		}
		
		build(lson), build(rson), psup(p);
		
		return ;
	}
	
	inline void work(int p, int k) {
		t[p].res = 1;
		t[p].cov = k;
		t[p].lc = t[p].rc = k;
		
		return ;
	}
	
	inline void psd(int p, int L, int R) {
		if(! t[p].cov) return ;
		
		work(ls(p), t[p].cov), work(rs(p), t[p].cov);
		
		t[p].cov = 0;
		
		return ;
	}
	
	inline Node merge(Node x, Node y) {
		Node z;
		
		z.lc = x.lc;
		z.rc = y.rc;
		z.res = x.res + y.res;
		
		if(x.rc == y.lc) -- z.res;
		
		return z;
	}
	
	inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) {
			work(p, k);
			
			return ;
		}
		
		psd(son);
		
		if(l <= mid) add(l, r, k, lson);
		if(r > mid) add(l, r, k, rson);
		
		psup(p);
		
		return ;
	}
	
	inline int ask(int x, int p = 1, int L = 1, int R = n) {
		if(L == R) return t[p].lc;
		psd(son);
		if(x <= mid) return ask(x, lson);
		else return ask(x, rson);
	}
	
	inline Node query(int l, int r, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) return t[p];
		
		psd(son);
		
		Node res = {-1, -1, 0, 0};
		
		if(r <= mid) return query(l, r, lson);
		if(l > mid) return query(l, r, rson);
		return merge(query(l, r, lson),query(l, r, rson));
	}
	
	inline void Add(int x, int y, int z) {
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			add(dfn[top[x]], dfn[x], z);
			
			x = fa[top[x]];
		}
		
		if(dep[x] > dep[y]) swap(x, y);
		
		add(dfn[x], dfn[y], z);
		
		return ;
	}
	
	inline int Query(int x, int y) {
		int res = 0, cx = -1, cy = -1;
		
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y), swap(cx, cy);
			
			res += query(dfn[top[x]], dfn[x]).res;
			
			int X = ask(dfn[x]), Y = ask(dfn[top[x]]);
			
			if(X == cx) -- res;
			cx = Y;
			x = fa[top[x]];
		}
		
		if(dep[x] > dep[y]) swap(x, y), swap(cx, cy);
		
		res += query(dfn[x], dfn[y]).res;
		int X = ask(dfn[x]), Y = ask(dfn[y]);
		if(X == cx) -- res;
		if(Y == cy) -- res;
		return res; 
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson
}

using namespace Segment_Tree;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	for(int i = 1 ; i < n ; ++ i) {
		cin >> u >> v;
		
		g[u].pb(v), g[v].pb(u);
	}
	
	dfs1(1, -1);
	top[1] = 1;
	dfs2(1);
	build();
	
	while(Q --) {
		cin >> op >> u >> v;
		
		if(op == 'C') {
			cin >> w;
			
			Add(u, v, w);
		}
		else cout << Query(u, v) << '\n';
	}
		
	return 0;
}

P3313 [SDOI2014] 旅行(待补)

  1. 对于每一种宗教维护一棵线段树,\(rt_i\) 表示宗教 \(i\) 线段树的根节点。
  2. 一种解决方法是使用动态开点线段树,build 时创建 \(O(n \log n)\) 个节点,\(Q\) 次修改共增加 \(O(Q \log n)\) 个节点,可行。
  3. 另一种思路是使用分块。维护 \(sum_{c, pos}\) 表示宗教 \(c\) 在第 \(pos\) 个块的权值和,\(mx_{c, pos}\) 表示宗教 \(c\) 在第 \(pos\) 个块的权值最大值。

P7735 [NOI2021] 轻重边

  1. 初始时给每个点随便赋 \(1-n\) 的不同权值,\(col\) 表示颜色。
  2. 对于操作 \(1\),每次将 \(col \to col + 1\),将 \(a \to b\) 的路径所有点染色为 \(col\)。只有 \(a \to b\) 路径上的边两个端点同色,路径上的点和路径外的邻接点颜色一定不同。
  3. 则重边为两端颜色相同的边,轻边为两端颜色不同的边。
  4. 操作 \(2\),转化为统计 \(a \to b\) 路径上连续相同的颜色段中重边的数量,线段树维护,类比 P2486 [SDOI2011] 染色

代码:

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

const int N = 1e5 + 5;
int T, n, Q, u, v, w, op, col, a[N];

namespace Graph {
	int times, id[N], sz[N], fa[N], dep[N], son[N], top[N], dfn[N];
	vector<int> g[N];
	
	inline void dfs1(int x, int last) {
		sz[x] = 1;
		
		for(auto u : g[x])
			if(u != last) {
				fa[u] = x;
				dep[u] = dep[x] + 1;
				
				dfs1(u, x);
				
				sz[x] += sz[u];
				if(sz[son[x]] < sz[u]) son[x] = u;
			}
		
		return ;
	}
	
	inline void dfs2(int x) {
		dfn[x] = ++ times;
		id[dfn[x]] = x;
		
		if(son[x]) {
			top[son[x]] = top[x];
			
			dfs2(son[x]);
		}
		
		for(auto u : g[x])
			if(! top[u]) {
				top[u] = u;
				
				dfs2(u);
			}
			
		return ;
	}
}

using namespace Graph;

namespace Segment_Tree {
	#define mid ((L + R) >> 1)
	#define son p, L, R
	#define lson ls(p), L, mid
	#define rson rs(p), mid + 1, R
	
	struct Node {
		int lc, rc, res, cov;
	} t[N << 2];
	
	inline int ls(int p) {
		return p << 1;
	}
	
	inline int rs(int p) {
		return p << 1 | 1;
	}
	
	inline void psup(int p) {
		t[p].lc = t[ls(p)].lc;
		t[p].rc = t[rs(p)].rc;
		t[p].res = t[ls(p)].res + t[rs(p)].res;
		
		if(t[ls(p)].rc == t[rs(p)].lc) ++ t[p].res;
		
		return ;
	}
	
	inline void build(int p = 1, int L = 1, int R = n) {
		t[p] = {-1, -1, 0, 0};
		
		if(L == R) {
			t[p].lc = t[p].rc = a[id[L]];
			
			return ;
		}
		
		build(lson), build(rson), psup(p);
		
		return ;
	}
	
	inline void work(int p, int L, int R, int k) {
		t[p].res = R - L;
		t[p].cov = k;
		t[p].lc = t[p].rc = k;
		
		return ;
	}
	
	inline void psd(int p, int L, int R) {
		if(! t[p].cov) return ;
		
		work(lson, t[p].cov), work(rson, t[p].cov);
		
		t[p].cov = 0;
		
		return ;
	}
	
	inline Node merge(Node x, Node y) {
		Node z;
		
		z.lc = x.lc;
		z.rc = y.rc;
		z.res = x.res + y.res;
		
		if(x.rc == y.lc) ++ z.res;
		
		return z;
	}
	
	inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) {
			work(son, k);
			
			return ;
		}
		
		psd(son);
		
		if(l <= mid) add(l, r, k, lson);
		if(r > mid) add(l, r, k, rson);
		
		psup(p);
		
		return ;
	}
	
	inline int ask(int x, int p = 1, int L = 1, int R = n) {
		if(L == R) return t[p].lc;
		
		psd(son);
		
		if(x <= mid) return ask(x, lson);
		else return ask(x, rson);
	}
	
	inline Node query(int l, int r, int p = 1, int L = 1, int R = n) {
		if(l <= L && R <= r) return t[p];
		
		psd(son);
		
		if(l > mid) return query(l, r, rson);
		if(r <= mid) return query(l, r, lson);
		
		return merge(query(l, r, lson), query(l, r, rson));
	}
	
	inline void Add(int x, int y, int z) {
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			
			add(dfn[top[x]], dfn[x], z);
			
			x = fa[top[x]];
		}
		
		if(dep[x] > dep[y]) swap(x, y);
		
		add(dfn[x], dfn[y], z);
		
		return ;
	}
	
	inline int Query(int x, int y) {
		int res = 0, cx = -1, cy = -1;
		
		while(top[x] != top[y]) {
			if(dep[top[x]] < dep[top[y]]) swap(x, y), swap(cx, cy);
			
			res += query(dfn[top[x]], dfn[x]).res;
			
			int X = ask(dfn[x]), Y = ask(dfn[top[x]]);
			
			if(X == cx) ++ res;
			cx = Y;
			x = fa[top[x]];
		}
		
		if(dep[x] > dep[y]) swap(x, y), swap(cx, cy);
		
		res += query(dfn[x], dfn[y]).res;
		int X = ask(dfn[x]), Y = ask(dfn[y]);
		if(X == cx) ++ res;
		if(Y == cy) ++ res;
		return res; 
	}
	
	#undef mid
	#undef son
	#undef lson
	#undef rson
}

using namespace Segment_Tree;

inline void init() {
	times = 0;
	for(int i = 1 ; i <= n ; ++ i) {
		g[i].clear();
		id[i] = sz[i] = fa[i] = dep[i] = son[i] = top[i] = dfn[i] = 0;
	}
	
	return ;
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> T;
	
	while(T --) {
		init();
		
		cin >> n >> Q;
		for(int i = 1 ; i <= n ; ++ i)
			a[i] = i;
		for(int i = 1 ; i < n ; ++ i) {
			cin >> u >> v;
			
			g[u].pb(v), g[v].pb(u);
		}
		
		col = n;
		dfs1(1, -1);
		top[1] = 1;
		dfs2(1);
		build();
		
		while(Q --) {
			cin >> op >> u >> v;
			
			if(op == 1) Add(u, v, ++ col);
			else cout << Query(u, v) << '\n';
		}
	}
		
	return 0;
}
posted @ 2024-09-09 21:34  endswitch  阅读(30)  评论(0)    收藏  举报