算法自学——树链剖分

参考资料:

树链剖分的含义

树链剖分就是将一棵树划分成若干条链,并保证每一条链上的dfs序连续,从而利用线段树对树上的信息进行维护。

重链剖分

在这里插入图片描述

实现

  • fa[i]:结点i的父结点的编号。
  • siz[i]:以结点i为根的子树的结点数。
  • dep[i]:结点i的深度。
  • son[i]:结点i的所有子结点中,siz值最大的子结点的编号。
  • dfn[i]:结点idfs序
  • top[i]:结点i所在链中,dep值最小的结点的编号。
  • rnk[i]dfs序i的结点的编号。

重链剖分就是要得到每个结点的上述信息,需要通过两个dfs来实现:

void dfs1(int x){	//得到fa、siz、dep、son
	siz[x] = 1;
	son[x] = 0;
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		if(to==fa[x]) continue;
		fa[to] = x;
		dep[to] = dep[x]+1;
		dfs1(to);
		siz[x] += siz[to];
		if(siz[to]>siz[son[x]]) son[x] = to;
	}
}
void dfs2(int x, int rt){	//得到dfn、top、rnk
	index++;
	dfn[x] = index;
	top[x] = rt;
	rnk[index] = x;
	if(son[x]!=0) dfs2(son[x], rt);
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		if(to==fa[x] || to==son[x]) continue;
		dfs2(to, to);
	}
}

路径上维护

注:代码中的modify()query都是线段树的操作,返回值均为void。

void modify_tree1(int x, int y, int v){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x, y);		//易错!
		modify(1, dfn[top[x]], dfn[x], v);
		x = fa[top[x]];
	}
	if(dep[x]<dep[y]) swap(x, y);
	modify(1, dfn[y], dfn[x], v);
}
int query_tree1(int x, int y){
	int res = 0;
	while(top[x]!=top[y]){	//直到两结点位于同一条链上为止
		if(dep[top[x]]<dep[top[y]]) swap(x, y);
		ans = 0;
		query(1, dfn[top[x]], dfn[x]);	//query()将结果保存在全局变量ans中
		res += ans;
		x = fa[top[x]];
	}
	if(dep[x]<dep[y]) swap(x, y);
	ans = 0;
	query(1, dfn[y], dfn[x]);
	res += ans;
	return res;
}

子树上维护

子树上的结点的dfs序是连续的。

void modify_tree2(int x, int v){
	modify(1, dfn[x], dfn[x]+siz[x]-1, v);
}
int query_tree2(int x){
	ans = 0;
	query(1, dfn[x], dfn[x]+siz[x]-1);
	return ans;
}

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

LCA

int LCA(int x, int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x, y);
		x = fa[top[x]];
	}
	if(dep[x]<dep[y]) swap(x, y);
	return y;
}

例题:P3379 【模板】最近公共祖先(LCA)

例1 P2590 [ZJOI2008]树的统计

题目描述

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

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

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

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

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

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

输入格式

输入文件的第一行为一个整数 n n n,表示节点的个数。

接下来 n − 1 n-1 n1 行,每行 2 2 2 个整数 a a a b b b,表示节点 a a a 和节点 b b b 之间有一条边相连。

接下来一行 n n n 个整数,第 i i i 个整数 w i w_i wi 表示节点 i i i 的权值。

接下来 1 1 1 行,为一个整数 q q q,表示操作的总数。

接下来 q q q 行,每行一个操作,以 CHANGE u t 或者 QMAX u v 或者 QSUM u v 的形式给出。

输出格式

对于每个 QMAX 或者 QSUM 的操作,每行输出一个整数表示要求输出的结果。

样例 #1

样例输入 #1

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

样例输出 #1

4
1
2
2
10
6
5
6
5
16

提示

对于 100 % 100 \% 100% 的数据,保证 1 ≤ n ≤ 3 × 1 0 4 1\le n \le 3\times 10^4 1n3×104 0 ≤ q ≤ 2 × 1 0 5 0\le q\le 2\times 10^5 0q2×105

中途操作中保证每个节点的权值 w w w − 3 × 1 0 4 -3\times 10^4 3×104 3 × 1 0 4 3\times 10^4 3×104 之间。

思路

本题为树剖路径上维护与查询的模板题。

代码

#include<bits/stdc++.h>
#define lc (i<<1)
#define rc (i<<1|1)
#define inf 0x7fffffff
using namespace std;

const int maxn = 3e4+5;

int n, q;
int a[maxn];

struct EDGE{
	int to;
	int next;
};

EDGE edge[maxn<<1];
int tot = 0;
int head[maxn];

void addEdge(int fr, int to){
	tot++;
	edge[tot].to = to;
	edge[tot].next = head[fr];
	head[fr] = tot;
}

int siz[maxn], dep[maxn], son[maxn], dfn[maxn], f[maxn], top[maxn], nfd[maxn];
int cnt = 0;

void dfs1(int x, int fa){
	siz[x] = 1;
	son[x] = 0;
	f[x] = fa;
	dep[x] = dep[fa]+1;
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		if(to == fa) continue;
		dfs1(to, x);
		siz[x] += siz[to];
		if(siz[to]>siz[son[x]]) son[x] = to;
	}
}

void dfs2(int x, int rt){
	cnt++;
	dfn[x] = cnt;
	nfd[cnt] = x;
	top[x] = rt;
	if(son[x] != 0) dfs2(son[x], rt);
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		if(to == f[x] || to == son[x]) continue;
		dfs2(to, to);
	}
}

struct TREE{
	int l, r;
	int M;
	int sum;
};

TREE tree[maxn<<2];

void pushUp(int i){
	tree[i].M = max(tree[lc].M, tree[rc].M);
	tree[i].sum = tree[lc].sum + tree[rc].sum;
}

void build(int i, int l, int r){
	tree[i].l = l, tree[i].r = r;
	if(l==r){
		tree[i].M = a[nfd[l]];		//易错
		tree[i].sum = a[nfd[l]];
		return;
	}
	int mid = (l+r)>>1;
	build(lc, l, mid);
	build(rc, mid+1, r);
	pushUp(i);
}

void modify(int i, int l, int r, int v){
	if(tree[i].l>=l&&tree[i].r<=r){
		tree[i].M = v;
		tree[i].sum = v;
	}
	else if(tree[i].r<l||tree[i].l>r){
		return;
	}
	else{
		modify(lc, l, r, v);
		modify(rc, l, r, v);
		pushUp(i);
	}
}

int ans = 0;

void queryMax(int i, int l, int r){
	if(tree[i].l>=l&&tree[i].r<=r){
		ans = max(ans, tree[i].M);
	}
	else if(tree[i].r<l||tree[i].l>r){
		return;
	}
	else{
		queryMax(lc, l, r);
		queryMax(rc, l, r);
	}
}

int queryMaxOnTree(int u, int v){
	int res = -inf;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u, v);
		ans = -inf;
		queryMax(1, dfn[top[u]], dfn[u]);	//易错:链头的dfs序最小
		res = max(res, ans);
		u = f[top[u]];
	}
	if(dep[u]<dep[v]) swap(u, v);
	ans = -inf;
	queryMax(1, dfn[v], dfn[u]);
	res = max(res, ans);
	return res;
}

void querySum(int i, int l, int r){
	if(tree[i].l>=l&&tree[i].r<=r){
		ans += tree[i].sum;
	}
	else if(tree[i].r<l||tree[i].l>r){
		return;
	}
	else{
		querySum(lc, l, r);
		querySum(rc, l, r);
	}
}

int querySumOnTree(int u, int v){
	int res = 0;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u, v);
		ans = 0;
		querySum(1, dfn[top[u]], dfn[u]);
		res += ans;
		u = f[top[u]];
	}
	if(dep[u]<dep[v]) swap(u, v);
	ans = 0;
	querySum(1, dfn[v], dfn[u]);
	res += ans;
	return res;
}

int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u, v;
		cin>>u>>v;
		addEdge(u, v);
		addEdge(v, u);
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs1(1, 0);
	dfs2(1, 1);
	build(1, 1, n);
	cin>>q;
	for(int i=1;i<=q;i++){
		string cmd;
		int u, v;
		cin>>cmd>>u>>v;
		if(cmd[1]=='H') modify(1, dfn[u], dfn[u], v);
		else if(cmd[1]=='M') cout<<queryMaxOnTree(u, v)<<'\n';
		else cout<<querySumOnTree(u, v)<<'\n';
	}
	return 0;
}

例2 P4315 月下“毛景树”

题目背景

毛毛虫经过及时的变形,最终逃过的一劫,离开了菜妈的菜园。 毛毛虫经过千山万水,历尽千辛万苦,最后来到了小小的绍兴一中的校园里。

题目描述

爬啊爬爬啊爬毛毛虫爬到了一颗小小的“毛景树”下面,发现树上长着他最爱吃的毛毛果~~ “毛景树”上有 N N N 个节点和 N − 1 N-1 N1 条树枝,但节点上是没有毛毛果的,毛毛果都是长在树枝上的。但是这棵“毛景树”有着神奇的魔力,他能改变树枝上毛毛果的个数:

  • Change k w:将第k条树枝上毛毛果的个数改变为 w w w 个。
  • Cover u v w:将节点 u u u 与节点 v v v 之间的树枝上毛毛果的个数都改变为 w w w 个。
  • Add u v w:将节点 u u u 与节点 v v v 之间的树枝上毛毛果的个数都增加 w w w 个。

由于毛毛虫很贪,于是他会有如下询问:

  • Max u v:询问节点 u u u 与节点 v v v 之间树枝上毛毛果个数最多有多少个。

输入格式

第一行一个正整数 N N N

接下来 N − 1 N-1 N1 行,每行三个正整数 U i , V i U_i,V_i Ui,Vi W i W_i Wi,第 i + 1 i+1 i+1 行描述第 i i i 条树枝。表示第 i i i 条树枝连接节点 U i U_i Ui 和节点 V i V_i Vi,树枝上有 W i W_i Wi 个毛毛果。 接下来是操作和询问,以 Stop 结束。

输出格式

对于毛毛虫的每个询问操作,输出一个答案。

样例 #1

样例输入 #1

4
1 2 8
1 3 7
3 4 9
Max 2 4
Cover 2 4 5
Add 1 4 10
Change 1 16
Max 2 4
Stop

样例输出 #1

9
16

提示

对于全部数据, 1 ≤ N ≤ 1 0 5 1\le N\le 10^5 1N105,操作和询问数目不超过 1 0 5 10^5 105

保证在任意时刻,所有树枝上毛毛果的个数都不会超过 1 0 9 10^9 109 个。

思路

本题为树剖维护边权的模板题,还涉及到了线段树的染色+修改,需要注意的细节较多。

代码

#include<bits/stdc++.h>
#define int long long
#define lc (i<<1)
#define rc (i<<1|1)
using namespace std;

const int maxn = 1e5+5;

struct NODE{
	int fr, to;
	int v;
};
NODE node[maxn];

struct EDGE{
	int fr, to, v;
	int next;
};
EDGE edge[maxn<<1];

int head[maxn];
int tot = 0;

void addEdge(int fr, int to, int v){
	tot++;
	edge[tot].to = to;
	edge[tot].v = v;
	edge[tot].next = head[fr];
	head[fr] = tot;
}

struct TREE{
	int l, r;
	int m;
	int coverTag = -1;
	int addTag = 0;
};
TREE tree[maxn<<2];

int siz[maxn], fa[maxn], son[maxn], dep[maxn], dfn[maxn], top[maxn], ndf[maxn];
int a[maxn];	//用点权代替边权 
int cnt = 0;

void dfs1(int x, int f){
	siz[x] = 1;
	son[x] = 0;
	fa[x] = f;
	dep[x] = dep[f]+1;
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to, v = edge[i].v;
		if(to==f) continue;
		a[to] = v;
		dfs1(to, x);
		siz[x] += siz[to];
		if(siz[to]>siz[son[x]]) son[x] = to; 
	}
}

void dfs2(int x, int rt){
	cnt++;
	dfn[x] = cnt;
	ndf[cnt] = x;
	top[x] = rt;
	if(son[x]!=0) dfs2(son[x], rt);
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		if(to==fa[x] || to==son[x]) continue;
		dfs2(to, to);
	}
}

void pushUp(int i){
	tree[i].m = max(tree[lc].m, tree[rc].m);
}

void pushDown(int i){		//注意pushdown顺序
	if(tree[i].coverTag!=-1){
		tree[lc].addTag = tree[rc].addTag = 0;
		tree[lc].m = tree[rc].m = tree[i].coverTag;
		tree[lc].coverTag = tree[rc].coverTag = tree[i].coverTag;
		tree[i].coverTag = -1;
	}
	if(tree[i].addTag!=0){
		tree[lc].addTag += tree[i].addTag;
		tree[lc].m += tree[i].addTag;
		tree[rc].addTag += tree[i].addTag;
		tree[rc].m += tree[i].addTag;
		tree[i].addTag = 0;
	}
} 

void build(int i, int l, int r){
	tree[i].l = l, tree[i].r = r;
	if(l==r){
		tree[i].m = a[ndf[l]];
		return;
	}
	int mid = (l+r)>>1;
	build(lc, l, mid);
	build(rc, mid+1, r);
	pushUp(i);	
}

void cover(int i, int l, int r, int v){
	if(tree[i].l>=l&&tree[i].r<=r){
		tree[i].coverTag = v;
		tree[i].addTag = 0;
		tree[i].m = v;
	}
	else if(tree[i].l>r||tree[i].r<l){
		return;
	}
	else{
		pushDown(i);
		cover(lc, l, r, v);
		cover(rc, l, r, v);
		pushUp(i);
	}
}

void add(int i, int l, int r, int v){
	if(tree[i].l>=l&&tree[i].r<=r){
		tree[i].addTag += v;
		tree[i].m += v;
	}
	else if(tree[i].l>r||tree[i].r<l){
		return;
	}
	else{
		pushDown(i);
		add(lc, l, r, v);
		add(rc, l, r, v);
		pushUp(i);
	}
} 

void modifyOnTree(int x, int y, int v, int cmd){	
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x, y);
		if(cmd==0) cover(1, dfn[top[x]], dfn[x], v);	//易错
		else add(1, dfn[top[x]], dfn[x], v);	
		x = fa[top[x]];
	}
	if(x==y) return;
	if(dep[x]<dep[y]) swap(x, y);
	if(cmd==0) cover(1, dfn[y]+1, dfn[x], v);		//易错
	else add(1, dfn[y]+1, dfn[x], v);	
} 

int ans = 0;

void query(int i, int l ,int r){
	if(tree[i].l>=l&&tree[i].r<=r){
		ans = max(ans, tree[i].m);
	}
	else if(tree[i].l>r||tree[i].r<l){
		return;
	}
	else{
		pushDown(i);
		query(lc, l, r);
		query(rc, l, r);
	}
}

int queryOnTree(int x, int y){
	int res = 0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x, y);
		ans = 0;
		query(1, dfn[top[x]], dfn[x]);
		res = max(res, ans);
		x = fa[top[x]];
	}
	if(x==y) return res;
	if(dep[x]<dep[y]) swap(x, y);
	ans = 0;
	query(1, dfn[y]+1, dfn[x]);
	res = max(res, ans);
	return res;	
} 

int n;

signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int u, v, w;
		cin>>u>>v>>w;
		node[i].fr = u, node[i].to = v, node[i].v = w;
		addEdge(u, v, w);
		addEdge(v, u, w);
	} 
	dfs1(1, 0);
	dfs2(1, 1);
	build(1, 1, n);
	while(1){
		string cmd;
		int u, v, w;
		cin>>cmd;
		if(cmd[0]=='S') break;
		else if(cmd[0]=='M'){
			cin>>u>>v;
			cout<<queryOnTree(u, v)<<'\n';
		}
		else if(cmd[0]=='A'){
			cin>>u>>v>>w;
			modifyOnTree(u, v, w, 1);
		}
		else{
			if(cmd[1]=='o'){
				cin>>u>>v>>w;
				modifyOnTree(u, v, w, 0);
			}
			else{
				cin>>u>>v;
				modifyOnTree(node[u].fr, node[u].to, v, 0);
			}
		}
	}
	return 0;
}

例3 P1967 [NOIP2013 提高组] 货车运输

题目描述

A 国有 n n n 座城市,编号从 1 1 1 n n n,城市之间有 m m m 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 q q q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 $ n,m$,表示 A 国有 $ n$ 座城市和 m m m 条道路。

接下来 m m m 行每行三个整数 x , y , z x, y, z x,y,z,每两个整数之间用一个空格隔开,表示从 x x x 号城市到 y y y 号城市有一条限重为 z z z 的道路。
注意: x ≠ y x \neq y x=y,两座城市之间可能有多条道路 。

接下来一行有一个整数 q q q,表示有 q q q 辆货车需要运货。

接下来 q q q 行,每行两个整数 x , y x,y x,y,之间用一个空格隔开,表示一辆货车需要从 x x x 城市运输货物到 y y y 城市,保证 x ≠ y x \neq y x=y

输出格式

共有 q q q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 − 1 -1 1

样例 #1

样例输入 #1

4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

样例输出 #1

3
-1
3

提示

对于 100 % 100\% 100% 的数据, 1 ≤ n < 1 0 4 1 \le n < 10^4 1n<104 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104 1 ≤ q < 3 × 1 0 4 1 \le q< 3\times 10^4 1q<3×104 0 ≤ z ≤ 1 0 5 0 \le z \le 10^5 0z105

思路

首先使用 Kruskal 算法求出最大生成树,如果最后得到是一片森林,则在森林之间建立权值为-1的边,使之成为一棵树。然后利用树剖算法对最大生成树进行划分,并将边的权值保存在深度较大的点上。利用线段树维护区间最小值,即可得到正确答案。

代码

#include<bits/stdc++.h>
#define INF 0x7fffffff
#define lc (i<<1)
#define rc (i<<1|1)
using namespace std;

const int maxn = 1e4+5;
const int maxm = 5e4+5;
const int maxq = 3e4+5;

int n, m, q;
int x, y, z;

struct NODE{
	int x, y, z;
	bool operator<(const NODE& a)const{
		return z>a.z;	//sort默认升序排序
	}
}; 
NODE node[maxm];

struct EDGE{
	int to;
	int v;
	int next;
};
EDGE edge[maxm<<1];

int head[maxn];
int tot = 0;

void addEdge(int fr, int to, int v){
	tot++;
	edge[tot].to = to;
	edge[tot].v = v;
	edge[tot].next = head[fr];
	head[fr] = tot;
}

int fa[maxn], rnk[maxn];

void init(int n){
	for(int i=1;i<=n;i++){
		fa[i] = i;
		rnk[i] = 1;
	}
}

int find(int x){
	return (x==fa[x]) ? x : (fa[x]=find(fa[x]));
}

void merge(int x, int y){
	int X = find(x), Y = find(y);
	if(X==Y) return;
	if(rnk[X]>rnk[Y]) fa[Y] = X;
	else if(rnk[Y]>rnk[X]) fa[X] = Y;
	else{
		fa[Y] = X;
		rnk[X]++;
	}
}

bool flag[maxn];
vector<int> temp;

void buildTree(){
	for(int i=1;i<=m;i++){
		int fr=node[i].x, to=node[i].y, v=node[i].z;
		if(find(fr)!=find(to)){
			addEdge(fr, to, v);
			addEdge(to, fr, v);
			merge(fr, to);
		}
	}
	for(int i=1;i<=n;i++){
		int I = find(i);
		if(!flag[I]){
			temp.push_back(I);
			flag[I] = 1;
		}
	}
	for(int j=1;j<temp.size();j++){
		addEdge(temp[0], temp[j], -1);
		addEdge(temp[j], temp[0], -1);
//			merge(i, j);
	}
}

int siz[maxn], son[maxn], dep[maxn], dfn[maxn], f[maxn], top[maxn], value[maxn], nfd[maxn];
int idx = 0;

void dfs1(int x, int fa){
	f[x] = fa;
	dep[x] = dep[fa]+1;
	siz[x] = 1;
	son[x] = 0;
	for(int i=head[x];i!=0;i=edge[i].next){
		int to=edge[i].to, v=edge[i].v;
		if(to==fa) continue;
		dfs1(to, x);
		value[to] = v;		//将边权保存在深度较大的点上
		siz[x] += siz[to];
		if(siz[to]>siz[son[x]]) son[x] = to;
	}
}

void dfs2(int x, int rt){
	idx++;
	dfn[x] = idx;
	nfd[idx] = x;
	top[x] = rt;
	if(son[x]!=0) dfs2(son[x], rt);
	for(int i=head[x];i!=0;i=edge[i].next){
		int to=edge[i].to, v=edge[i].v;
		if(to==f[x] || to==son[x]) continue;
		dfs2(to, to);
	}
}

struct TREE{
	int l, r;
	int v;
};
TREE tree[maxn<<2];

void pushUp(int i){
	tree[i].v = min(tree[lc].v, tree[rc].v);
}

void build(int i, int l, int r){
	tree[i].l = l, tree[i].r = r;
	if(l==r){
		tree[i].v = value[nfd[l]];		//线段树中的l、r对应的是dfs序
		return;
	}
	int mid = (l+r)>>1;
	build(lc, l, mid);
	build(rc, mid+1, r);
	pushUp(i);
}

int ans = INF;

void query(int i, int l, int r){
	if(tree[i].l>=l&&tree[i].r<=r){
		ans = min(ans, tree[i].v);
	}
	else if(tree[i].l>r||tree[i].r<l) return;
	else{
		query(lc, l, r);
		query(rc, l, r);
	}
}

int queryTree(int x, int y){
	int rtn = INF;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x, y);
		ans = INF;
		query(1, dfn[top[x]], dfn[x]);
		rtn = min(ans, rtn);
		x = f[top[x]];
	}	
	if(dep[x]<dep[y]) swap(x, y);
	ans = INF;
	query(1, dfn[y]+1, dfn[x]);
	rtn = min(ans, rtn);
	return rtn;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>node[i].x>>node[i].y>>node[i].z;
	}
	sort(node+1, node+m+1);
	init(n);
	buildTree();
	value[1] = INF;
	dfs1(1, 0);
	dfs2(1, 1);
	build(1, 1, n);
	cin>>q;
	for(int i=1;i<=q;i++){
		int x, y;
		cin>>x>>y;
		if(find(x)!=find(y)) cout<<-1;
		else cout<<queryTree(x, y);
		cout<<'\n';
	}
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值