树分治
引
Empty
本文同步发表在 博客园
静态树分治
这个算法可以处理关于树上所有链信息的问题,由于枚举所有链的复杂度绝对是
树分治是一个暴力数据结构。
它可以在
边分治
找一条边,把树分成两半。在两边子树找两点,则有一条过这条边的路径。
再对两个子树分治。
可是菊花图会被卡成
使用 “左二子,右兄弟” 的方法可以优化。
可是也有局限性(我并不是太了解边分治)。
点分治。
相比于边分治,点分治更通用一些,后面的点分树也是由此转化得来。
我们每次找到一个点
删去
再对
这样可以 “遍历” 所有路径。
每次分治的中心选取 重心 是最优的(每次子树大小除以
于是时间复杂度是
所以我们可以每次遍历所有分治中心的子树,暴力计算答案。
一般有两种方式计算答案。
-
容斥。用所有点两两的答案减去子树内的答案。
-
数据结构维护。每次用遍历当前子树,用数据结构查询之前子树的答案,在把当前字树加入数据结构。
代码实例:
int sze[400010], dp[400010], vis[400010]; // vis:是否当做过分治中心
int tot, rt;
void getrt(int u, int fa) {
sze[u] = 1, dp[u] = 0;
for (auto y : edge[u]) {
int v = y.first;
if (vis[v] || v == fa)
continue;
getrt(v, u);
sze[u] += sze[v];
dp[u] = max(dp[u], sze[v]);
}
dp[u] = max(dp[u], sum - sze[u]);
if (dp[u] < dp[rt])
rt = u;
}
void solve(int u) {
vis[u] = 1;
ans += getans(u); // 以容斥计算答案为例
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v])
continue;
ans -= getans(v); //
sum = sze[v];
dp[0] = n, rt = 0;
getrt(v, u);
solve(rt);
}
}
好了,至此我们已经学会了点分治,下面要开始实战了。
先来一道最基础的题:
例1.1 Tree
给定大小为
求长度小于等于
考虑把和式
它等于所有的
即
可以用容斥算。
对于
看代码吧:
void getrt(int u, int fa) {
sze[u] = 1, dp[u] = 0;
for (auto y : edge[u]) {
int v = y.first;
if (vis[v] || v == fa)
continue;
getrt(v, u);
sze[u] += sze[v];
dp[u] = max(dp[u], sze[v]);
}
dp[u] = max(dp[u], sum - sze[u]);
if (dp[u] < dp[rt])
rt = u;
}
void getdis(int u, int fa) {
rev[++tot] = d[u];
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (!vis[v] && v != fa) {
d[v] = d[u] + w;
getdis(v, u);
}
}
}// 算距离
int getans(int u, int w) {
tot = 0, d[u] = w;
getdis(u, 0);
sort(rev + 1, rev + tot + 1);
int l = 1, r = tot, tmp = 0;
while (l <= r) {
if (rev[l] + rev[r] <= k)
tmp += r - l, ++l;
else
r--;
}
return tmp;
}// 得到 rev 数组里两两的答案
void solve(int u) {
vis[u] = 1;
ans += getans(u, 0);// 加上所有的点的答案。
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v])
continue;
ans -= getans(v, w); // 减去子树内的答案,得到两两子树的答案
sum = sze[v];
dp[0] = n, rt = 0;
getrt(v, u);
solve(rt);
}
}
练1.1.1 聪聪可可
P2634 [国家集训队] 聪聪可可
题意:给你一颗 带权 树,求有多少条路径满足长度是
sol:这算是刚才那道题改了一下(更简单了)。
还是利用容斥。
一样的,答案为:
容易计算。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
#define int long long
vector<pair<int, int> > edge[N];
int n;
int sze[N], dp[N];
int vis[N];
int d[N];
int tot, rt, sum;
int rev[N];
int ans;
void getrt(int u, int fa) {
sze[u] = 1, dp[u] = 0;
for (auto y : edge[u]) {
int v = y.first;
if (vis[v] || v == fa)
continue;
getrt(v, u);
sze[u] += sze[v];
dp[u] = max(dp[u], sze[v]);
}
dp[u] = max(dp[u], sum - sze[u]);
if (dp[u] < dp[rt])
rt = u;
}
void getdis(int u, int fa) {
rev[++tot] = d[u];
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (!vis[v] && v != fa) {
d[v] = d[u] + w;
getdis(v, u);
}
}
}
int getans(int u, int w) {
tot = 0, d[u] = w;
getdis(u, 0);
int s[3] = { 0, 0, 0 };
for (int i = 1; i <= tot; i++) {
s[rev[i] % 3]++;
}
return s[0] * s[0] + s[1] * s[2] * 2;
}
void solve(int u) {
vis[u] = 1;
ans += getans(u, 0);
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v])
continue;
ans -= getans(v, w);
sum = sze[v];
dp[0] = n, rt = 0;
getrt(v, u);
solve(rt);
}
}
int gcd(int a, int b) { return (b == 0) ? a : gcd(b, a % b); }
signed main() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
edge[u].push_back({ v, w });
edge[v].push_back({ u, w });
}
dp[0] = sum = n;
getrt(1, 0);
solve(rt);
cout << ans / gcd(ans, n * n) << "/" << n * n / gcd(ans, n * n);
return 0;
}
练1.1.2 重建计划
(P4292 [WC2010] 重建计划)[https://www.luogu.com.cn/problem/P4292]
题意:
给你一棵带权树:求一条边数在
设边权为
答案为:
二分答案
即
即
每次二分先把边权减去
我们要求最长的一条路径的长度
还是点分治。每次用线段树维护边数在一段区间的答案,可是这样做的时间复杂度为
注意到每次查询的区间为
按照子树内
这样做还会有问题,单调队列的大小由最深的子树决定。最坏复杂度为
所以我们得按照子树深度从小到大的顺序枚举。
时间复杂度
代码:
这里放的是同学的代码,因为我是用
#include <bits/stdc++.h>
using namespace std;
typedef int ll;
typedef double ld;
const ll Pig = 2e5 + 10;
ll n, L, R, dis[Pig], cnt[Pig], ans, sze[Pig], p[Pig], cur, ln[Pig], len;
ld d[Pig], buc[Pig], val;
vector<pair<ll, ll> > g[Pig];
vector<ll> pnt;
bitset<Pig> vis;
void dfs_ln(ll i, ll f, ll t) {
dis[i] = dis[f] + 1;
ln[t] = max(ln[t], dis[i]);
for (auto j : g[i]) {
if (j.first == f or vis[j.first])
continue;
dfs_ln(j.first, i, t);
}
}
void dfs1(ll i, ll f) {
sze[i] = 1;
p[i] = 0;
for (auto j : g[i]) {
if (j.first == f or vis[j.first])
continue;
ll v = j.first, w = j.second;
d[v] = d[i] + w - val;
dis[v] = dis[i] + 1;
dfs1(v, i);
sze[i] += sze[v];
p[i] = max(p[i], sze[v]);
}
}
void dfs2(ll i) {
if (ans)
return;
vector<ll> v, curr;
vector<pair<ll, ll> > gg;
buc[0] = 0;
dis[i] = 1;
vis[i] = 1;
len = 0;
for (auto j : g[i]) {
if (!vis[j.first]) {
ln[j.first] = 0;
dfs_ln(j.first, i, j.first);
gg.emplace_back(j);
}
}
sort(gg.begin(), gg.end(), [&](pair<ll, ll> a, pair<ll, ll> b) { return ln[a.first] < ln[b.first]; });
for (auto j : gg) {
cur = j.first;
d[cur] = j.second - val;
dis[j.first] = 1;
dfs1(cur, i);
queue<ll> q;
deque<ll> c;
vector<ll> point;
q.emplace(cur);
ll r = -1;
while (!q.empty()) {
ll pt = q.front();
p[pt] = max(p[pt], sze[j.first] - sze[pt]);
if (p[pt] < p[cur])
cur = pt;
q.pop();
point.emplace_back(pt);
for (auto k : g[pt])
if (dis[k.first] > dis[pt])
q.emplace(k.first);
}
reverse(point.begin(), point.end());
for (ll k : point) {
while (r < R - dis[k] and r < len) {
r++;
while (!c.empty() and buc[c.back()] < buc[r]) c.pop_back();
c.emplace_back(r);
}
while (!c.empty() and c.front() + dis[k] < L) c.pop_front();
if (!c.empty() and buc[c.front()] + d[k] >= 0)
ans = 1;
}
for (ll k : point) buc[dis[k]] = max(buc[dis[k]], d[k]), curr.emplace_back(k), len = max(len, dis[k]);
v.emplace_back(cur);
if (ans) {
vis[i] = 0;
buc[0] = buc[Pig - 1];
for (ll k : curr) buc[dis[k]] = buc[Pig - 1];
return;
}
}
buc[0] = buc[Pig - 1];
for (ll k : curr) buc[dis[k]] = buc[Pig - 1];
if (ans) {
vis[i] = 0;
return;
}
for (ll j : v) dfs2(j);
vis[i] = 0;
}
ll read() {
char c = getchar();
ll res = 0;
bool flg = 1;
while (!isdigit(c)) {
if (c == '-')
flg = 0;
c = getchar();
}
while (isdigit(c)) res = (res << 3) + (res << 1) + (c ^ '0'), c = getchar();
if (!flg)
res = -res;
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.setf(ios::fixed);
cout.precision(3);
memset(buc, -0x3f, sizeof(buc));
n = read();
L = read();
R = read();
bool flg = 1;
for (ll i = 1, u, v, w; i < n; i++) {
u = read();
v = read();
w = read();
g[u].emplace_back(v, w);
g[v].emplace_back(u, w);
}
ld l = 0, r = 1e6;
while (fabs(l - r) > 4.5e-4) {
val = (l + r) / 2;
ans = 0;
dfs2(1);
if (ans)
l = val;
else
r = val;
}
cout << (l + r) / 2;
return 0;
}
练1.1.3 Yin and Yang G
P3085 [USACO13OPEN] Yin and Yang G
题意:给你一棵边权为
sol:
等价于求
设分治中心为
假设在它的子树中有两点
我们按照一个点
没有的点只能与有的点配成一对,而有的点可以和两种点配对。
这个用数组统计。需要注意的是要仔细考虑
代码: 为了避免负数,我将下标平移了一段区间,也可以使用 map 解决。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int n;
vector<pair<int, int> > edge[N];
int dp[N], sze[N], vis[N], cnt[N], res[N], cnt1[N];
int rt, sum, ans;
void getrt(int u, int f) {
dp[u] = 0, sze[u] = 1;
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v] || v == f)
continue;
getrt(v, u);
sze[u] += sze[v];
dp[u] = max(dp[u], sze[v]);
}
dp[u] = max(dp[u], sum - sze[u]);
if (dp[u] < dp[rt])
rt = u;
}
int mn, mx;
bool flag = 0;
void getans(int u, int f, int dis) {
ans += (res[dis + 100000] > 0) * cnt[-dis + 100000] + cnt1[-dis + 100000];
if (dis == 0 && res[dis + 100000] > 1)
ans++;
res[dis + 100000]++;//统计当前点到 $r$ 的 $d$
mn = min(mn, dis);
mx = max(mx, dis);
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v] || v == f)
continue;
getans(v, u, dis + w);
}
res[dis + 100000]--;
}
void getdis(int u, int f, int dis) {
cnt[dis + 100000] += (res[dis + 100000] == 0);
cnt1[dis + 100000] += (res[dis + 100000] > 0);
res[dis + 100000]++;//统计当前点到 $r$ 的 $d$
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v] || v == f)
continue;
getdis(v, u, dis + w);
}
res[dis + 100000]--;
}
void solve(int u) {
vis[u] = 1;
res[100000] = 1;
mn = 1e5, mx = -1e5;
flag = 0;
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v])
continue;
getans(v, u, w);
getdis(v, u, w);
}
for (int i = mn; i <= mx; i++) res[i + 100000] = cnt[i + 100000] = cnt1[i + 100000] = 0;
for (auto y : edge[u]) {
int v = y.first, w = y.second;
if (vis[v])
continue;
sum = sze[v];
rt = 0, dp[rt] = n + 1;
getrt(v, u);
solve(rt);
}
}
signed main() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
if (w == 0)
w = -1;
edge[u].push_back({ v, w });
edge[v].push_back({ u, w });
}
sum = n + 1;
rt = 0, dp[rt] = n + 1;
getrt(1, 0);
solve(rt);
cout << ans;
return 0;
}
广义点分治
传统的点分治帮助我们快速统计树上所有路径。
可以发现分治的层数 不超过
于是一类问题出现了,问一棵树上的最优点
怎么刻画最优是题目规定的,如带权重心(P3345 幻想乡战略游戏)。
我们先假设
如果这样的点只有一个,且满足单调性:若这个点的答案不优,则它的子树的答案一定更不优。
所以答案一定在满足
我们可以从
则最优点一定在点分树的子子树里。
例1.2 快递员
题意: (不想改题面啦qwq)
Showson 的城市里面有
Showson 需要送
现在 Showson 希望确定一个点作为集散中心的开设位置,使得他送货所需的最长距离最小。显然,这个最长距离是个偶数,你只需要输出最长距离除以
sol:
点分治。
设现在的快递中心为
先暴力求出快递中心到所有点对的距离和。
找到所有使答案最大的点对。
若
若两组点对所在的子树不同,答案也无法再小。
假如可以再小。
就往那个子树分治。
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N = 1e5 + 10;
int n, m;
vector<pair<int, int> > edge[N];
int vis[N], dp[N], sze[N], tot, rt;
struct node{
int x, y;
}a[N];
void getrt(int u, int f) {
dp[u] = 0, sze[u] = 1;
for(auto y : edge[u]) {
int v = y.first, w = y.second;
if(vis[v] || v == f) continue;
getrt(v, u);
dp[u] = max(dp[u], sze[v]);
sze[u] += sze[v];
}
dp[u] = max(dp[u], tot - sze[u]);
if(dp[u] < dp[rt]) rt = u;
}
int d[N], sub[N], ans = 1e9;
void getdis(int u, int f, int s) {
sub[u] = s;
for(auto y : edge[u]) {
int v = y.first, w = y.second;
if(v == f) continue;
d[v] = d[u] + w;
getdis(v, u, s);
}
}
void print() {
cout << ans;
exit(0);
}
int solve(int u) {
if(vis[u]) print();
vis[u] = 1;
d[u] = 0;
for(auto y : edge[u]) {
int v = y.first, w = y.second;
d[v] = w;
getdis(v, u, v);
}
int mx = 0;
vector<int> v;
for(int i = 1; i <= m; i++) {
int x = a[i].x, y = a[i].y;
if(d[x] + d[y] > mx) v.clear(), v.push_back(i), mx = d[x] + d[y];
else if(d[x] + d[y] == mx) v.push_back(i);
}
if(mx < ans) ans = mx;
int lst = 0;
for(auto i : v) {
int x = a[i].x, y = a[i].y;
if(sub[x] != sub[y]) print();
if(lst == 0) lst = sub[x];
if(lst != sub[x]) print();
}
rt = 0, dp[0] = n + 1, tot = sze[lst];
getrt(lst, u);
solve(rt);
}
signed main() {
cin >> n >> m;
for(int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
edge[u].push_back({v, w});
edge[v].push_back({u, w});
}
for(int i = 1; i <= m; i++) {
cin >> a[i].x >> a[i].y;
}
rt = 0, dp[0] = n + 1, tot = n;
getrt(1, 0);
solve(rt);
print();
return 0;
}
练2.2.1 幻想乡战略游戏
题意:
有一棵带权树。
找到一个点
Sol:
这道题要用点分树。
请学习完点分树以后再来查看。
做法差不多。
设
设当前最优
可以点分树时对每个点开
代码: 二分:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int n, M;
vector<pair<int, int> > edge[N];
int a[N];
struct SGT{
vector<pair<int, int> > v;
void change(int x, int y) {
v.push_back({x, y});
}
void init() {
sort(v.begin(), v.end());
int sze = v.size();
for(int i = 1; i < sze; i++) {
v[i].second += v[i - 1].second;
}
}
int query(int x) {
auto p = upper_bound(v.begin(), v.end(), make_pair(x, (int)1e14));
if(p == v.begin()) return 0;
p--;
return (*p).second;
}
int query(int l, int r) {
return query(r) - query(l - 1);
}
}w1[N], w2[N], w3[N];
int Fa[N][20], dep[N], dis[N], lg2[N];
void dfs0(int u, int f) {
Fa[u][0] = f, dep[u] = dep[f] + 1;
for(int i = 1; i <= 19; i++) Fa[u][i] = Fa[Fa[u][i - 1]][i - 1];
for(auto y : edge[u]) {
int v = y.first, w = y.second;
if(v != f) dis[v] = dis[u] + w, dfs0(v, u);
}
}
int Lca(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
while(dep[x] > dep[y]) x = Fa[x][lg2[dep[x] - dep[y]]];
if(x == y) return x;
for(int i = 19; i >= 0; i--) if(Fa[x][i] != Fa[y][i]) x = Fa[x][i], y = Fa[y][i];
return Fa[x][0];
}
int getdis(int x, int y) {
return dis[x] + dis[y] - 2 * dis[Lca(x, y)];
}
int dp[N], sze[N], vis[N], s[N], cnt, rt;
void getrt(int u, int f) {
sze[u] = 1, dp[u] = 0;
for(auto y : edge[u]) {
int v = y.first, w = y.second;
if(v == f || vis[v]) continue;
getrt(v, u);
sze[u] += sze[v];
dp[u] = max(dp[u], sze[v]);
}
dp[u] = max(dp[u], cnt - sze[u]);
if(dp[u] < dp[rt]) rt = u;
}
int fa[N];
void init(int u) {
vis[u] = 1;
s[u] = cnt;
for(auto y : edge[u]) {
int v = y.first, w = y.second;
if(vis[v]) continue;
rt = 0, dp[0] = n + 1, cnt = sze[v];
getrt(v, u);
fa[rt] = u;
init(rt);
}
}
void modify(int u) {
for(int i = u; i ; i = fa[i]) w1[i].change(a[u], getdis(u, i));
for(int i = u; fa[i]; i = fa[i]) w2[i].change(a[u], getdis(u, fa[i]));
for(int i = u; i; i = fa[i]) w3[i].change(a[u], 1);
}
int query(int u, int l, int r) {
int res = w1[u].query(l, r);
for(int i = u; fa[i] ; i = fa[i]) {
res = res + w1[fa[i]].query(l, r) - w2[i].query(l, r) + getdis(u, fa[i]) * (w3[fa[i]].query(l, r) - w3[i].query(l, r));
}
return res;
}
signed main() {
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin >> n >> T >> M;
for(int i = 1; i <= n; i++) {
cin >> a[i]; a[i]++;
}
for(int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
edge[u].push_back({v, w});
edge[v].push_back({u, w});
}
dfs0(1, 0);
rt = 0, dp[0] = n + 1, cnt = n;
getrt(1, 0);
init(rt);
for(int i = 1; i <= n; i++) lg2[i] = log2(i);
for(int i = 1; i <= n; i++) modify(i);
for(int i = 1; i <= n; i++) w1[i].init(), w2[i].init(), w3[i].init();
int ans = 0;
while(T--) {
int u, a, b;
cin >> u >> a >> b;
int L = min((a + ans) % M, (b + ans) % M) + 1, R = max((a + ans) % M, (b + ans) % M) + 1;
cout << (ans = query(u, L, R)) << "\n";
}
return 0;
}
动态开店线段树版,只有结构体有变化
struct SGT {
vector<pair<int, int> > v;
void change(int x, int y) { v.push_back({ x, y }); }
void init() {
sort(v.begin(), v.end());
int sze = v.size();
for (int i = 1; i < sze; i++) {
v[i].second += v[i - 1].second;
}
}
int query(int x) {
auto p = upper_bound(v.begin(), v.end(), make_pair(x, (int)1e14));
if (p == v.begin())
return 0;
p--;
return (*p).second;
}
int query(int l, int r) { return query(r) - query(l - 1); }
} w1[N], w2[N], w3[N];
练 2.1.2 P5311 [Ynoi2011] 成都七中
P5311 [Ynoi2011] 成都七中
题意:
给你一棵
查询操作给定参数
将树中编号在
每次查询操作独立。
Sol:
点分树有性质:树上任意一个联通块,存在一个在点分树上深度最小的点,并且整个联通块都在这个点的子树当中。
证明:
问题变成:在一棵树中,从根节点出发,只经过
使用反证法。
假设点分树上最浅的节点叫做
所以我们可以把每一个询问
注意要判断这个点是否能走到点分树上的祖先节点。
我们记录一下每一个节点到(点分树上的)每个祖先的路径上的编号最大的点和编号最小的点,分别设成
我们发现,只有对于
将询问离线一波,第一维排序第二维树状数组维护即可解决。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
vector<int> edge[N];
int c[N];
void add(int x, int y) {
for (; x <= n; x += x & -x) c[x] += y;
}
int query(int x) {
if (!x)
return 0;
int res = 0;
for (; x; x -= x & -x) res += c[x];
return res;
}
void del(int x) {
for (; x <= n; x += x & -x) c[x] = 0;
}
struct node {
int l, r, op;
};
int a[N];
int ans[N];
vector<node> q[N];
int vis[N], dp[N], sze[N], rt, cnt;
void getrt(int u, int f) {
dp[u] = 0, sze[u] = 1;
for (auto v : edge[u]) {
if (v == f || vis[v])
continue;
getrt(v, u);
dp[u] = max(dp[u], sze[v]);
sze[u] += sze[v];
}
dp[u] = max(dp[u], cnt - sze[u]);
if (dp[u] < dp[rt])
rt = u;
}
int mn[N], mx[N], col[N];
vector<node> t;
void getres(int u, int f) {
mn[u] = min(mn[f], u), mx[u] = max(mx[f], u);
t.push_back({ mn[u], mx[u], -a[u] });
for (auto x : q[u]) {
if ((!ans[x.op]) && x.l <= mn[u] && mx[u] <= x.r)
t.push_back({ x.l, x.r, x.op });
}
for (auto v : edge[u]) {
if (vis[v] || v == f)
continue;
getres(v, u);
}
}
bool cmp(node x, node y) {
if (x.l != y.l)
return x.l > y.l;
return x.op < y.op;
}
void solve(int u) {
vis[u] = 1;
t.clear();
mx[0] = -1e9, mn[0] = 1e9;
getres(u, 0);
sort(t.begin(), t.end(), cmp);
for (auto x : t) {
if (x.op < 0) {
x.op *= -1;
if (!col[x.op])
add(x.r, 1), col[x.op] = x.r;
else if (x.r < col[x.op]) {
add(col[x.op], -1);
add(x.r, 1);
col[x.op] = x.r;
}
} else {
ans[x.op] = query(x.r) - query(x.l - 1);
}
}
for (auto x : t) {
if (x.op < 0)
del(x.r), col[-x.op] = 0;
}
for (auto v : edge[u]) {
if (vis[v])
continue;
rt = 0, dp[0] = n + 1, cnt = sze[v];
getrt(v, u);
solve(rt);
}
}
int main() {
int T;
cin >> n >> T;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
for (int i = 1; i <= T; i++) {
int l, r, x;
cin >> l >> r >> x;
q[x].push_back({ l, r, i });
}
rt = 0, dp[0] = n + 1, cnt = n;
getrt(1, 0);
solve(rt);
for (int i = 1; i <= T; i++) cout << ans[i] << "\n";
return 0;