补题链接:Here
LCA 算法讲解:Here
考虑用 (f[i][j]) 表示从i往上走,能买珠宝的第 (2^j) 个点是哪个,显然,如果我们知道每个 (f[i][0])的值, 那么 (f[i][j]=f[f[i][j−1]][j−1]) ( i 往上的第2 ^j−1 个点再往上 2^j−1个点)。那么 (f[i][0]) 怎么求呢?肯定不能暴力,因为暴力最坏情况可能会让你每次都跑到根(构造一条链,从根到叶子价值递增)。这个其实也可也倍增着跳——如果 i 的父亲fa比i 大那自不必说,当i的父亲fa比i 小,那么我们可以看fa的父亲向上走 (2^k)(k从大到小枚举)个能买进的点的权值(即 (f[fa][k])),如果这个点权限比 i 点权值小,说明还需要往上走,就跳到f[fa] [k]上并把跳跃的长度减少一半(如果还是刚刚的跳跃高度,从 (f[fa][k])往上跳 (2 ^k)
长度,实际上就是从fa跳 2^k+1 ,相当于就是跳到 (f[fa][k+1])了);如果 (f[fa)][k]这个点的权值比 i 大,说明跳多了,就只是把跳跃长度减少一半再看。
注意我这里说的跳跃长度都是按照能买东西的点的个数计数,相当于我的f数组其实是对原树进行了重建,每个点往上走1步都的连向的它能到的第一个比他大的点(这样理解也许会容易很多——即我们对原树进行变形,每个点都连向它上方第一个能买东西的点,构成一个新的森林,于是问题就变成了,从u走到自己上方深度不小于dep[v]的点需要经过多少个点)。
有了这个值之后我们要求从 u 往上走到 v 经过了多少点,也可也倍增去求了——即从 u 出发尝试往上跳 (2^k) 高度,如果已经跳过 v 了,就减小 k,如果没有跳到 v 的上方,就先跳上去再减小 k。 注意,因为f数组存的能买东西的点,很有可能v根本不在这里面,所有跳的时候是通过判断深度来判断是否结束的。
这道题想了好久好久 最终还是参考清楚姐姐的思路才想明白 /(ㄒoㄒ)/~~
// Murabito-B 21/04/10
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
typedef long long ll;
vector<int> v[maxn];
int a[maxn], f[maxn][20], dep[maxn], to[maxn];
void dfs(int p, int fa) {
int x = fa;
for (int k = 19; k >= 0; k--)
if (f[x][k] && a[f[x][k]] <= a[p]) x = f[x][k]; //如果x往上跳2^k步这个点存在,且这个点的权值比a[p]要小,就跳到这个点再往上跳
if (a[x] > a[p]) f[p][0] = x; //判断比a[p]大的点到底是x还是x的上面那个点
else
f[p][0] = f[x][0];
//向上倍增的找到第一个比p权值大的点
for (int i = 1; i < 20; i++) f[p][i] = f[f[p][i - 1]][i - 1];
dep[p] = dep[fa] + 1; //维护高度
int num = v[p].size();
for (int i = 0; i < num; i++) {
int to = v[p][i];
if (to == fa) continue;
dfs(to, p); //搜子树
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
v[x].push_back(y);
v[y].push_back(x);
}
for (int i = n + 1; i <= n + q; i++) {
int x, y, c;
cin >> x >> y >> c;
v[i].push_back(x);
v[x].push_back(i);
a[i] = c;
to[i - n] = y; //记录对应的终点
}
dfs(1, 0);
for (int i = 1; i <= q; i++) {
int ans = 0;
int x = i + n; //i+n是第i个询问的起点
for (int k = 19; k >= 0; k--) { //k从大到小枚举
if (dep[f[x][k]] >= dep[to[i]]) { //如果跳完之后深度小于目标点深度就已经走过了,跳跃高度要减小
ans += (1 << k); //点数加2^k
x = f[x][k]; //向上跳2^k个点
}
}
cout << ans << endl;
}
return 0;
}