题目描述
有一个树状的城市网络(即 n 个城市由 n-1 条道路连接的连通图),首都为 1 号城市,每个城市售卖价值为 a_i 的珠宝。
你是一个珠宝商,现在安排有 q 次行程,每次行程为从 u 号城市前往 v 号城市(走最短路径),保证 v 在 u 前往首都的最短路径上。 在每次行程开始时,你手上有价值为 c 的珠宝(每次行程可能不同),并且每经过一个城市时(包括 u 和 v ),假如那个城市中售卖的珠宝比你现在手上的每一种珠宝都要优秀(价值更高,即严格大于),那么你就会选择购入。
现在你想要对每一次行程,求出会进行多少次购买事件。
解题思路
倍增+dfs
题目表明v一定在u去网1的最短路径上,所以朴素的思想就是把u一个个向上提,再去判断到达的每一个城市,需不需要购买。
这种算法的极端情况,斜树+叶子与根,时间复杂度达到了O(nq),后面给卡掉了。
能不能优化这个算法,我们知道,从u向上找v,这个好像和LCA比较相似,那我们能不能根据倍增的思路,去把寻找这个过程优化呢。
当然是有的,倍增的强大思路就来了,首先我们选择离线全部的查询,把每一个起点当作是新节点的父节点,终点另外一个数组记录,自己手上珠宝权值也是另外记录记作新点的点权。那么我们可以假设:
dp[i][j]是从i节点开始,购买 次珠宝到达的节点
那么根据倍增的递推式可以得到dp[i][j]=dp[dp[i][j-1]][j-1]
因为dfs的原因,父节点都已经处理完毕,可以更新子节点部分。
最后查询的时候,从起点出发,如果购买一定次数到达的节点深度小于终点,说明可以在去v的路上购买这些次数的珠宝。
Code
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 2e5 + 7;
vector<int> e[N];
// 价值 深度 终点
int dp[N][20], a[N], dep[N], to[N];
// dp[i][j] 记录从i出发购买2^j次珠宝到达的节点
void dfs(int x, int fa) {
int tmp = fa; //记录初始父亲,防止死循环
dep[x] = dep[fa] + 1;
for (int i = 19; i >= 0; --i)
if (dp[fa][i] && a[dp[fa][i]] <= a[x]) //从父节点出发,向上寻找最远的大于a[x]的节点
fa = dp[fa][i];
if (a[fa] > a[x]) dp[x][0] = fa; //如果fa的值仍然大于a[x],那么购买2^0定义为fa
else dp[x][0] = dp[fa][0]; // 否则就把从fa买1次的终点定义给x买一次的终点
for (int i = 1; i < 20; ++i)
dp[x][i] = dp[dp[x][i - 1]][i - 1];
for (auto it : e[x])
if (it != tmp) dfs(it, x);
}
int main() {
int n = read(), m = read();
for (int i = 1; i <= n; ++i) a[i] = read();
for (int i = 1; i < n; ++i) {
int x = read(), y = read();
e[x].push_back(y);
e[y].push_back(x);
}
for (int i = n + 1; i <= n + m; ++i) {
int x = read(), y = read(), z = read();
e[x].push_back(i);
e[i].push_back(x);
to[i] = y; a[i] = z;
}
dfs(1, 0);
for (int i = n + 1; i <= n + m; ++i) {
ll cnt = 0, tmp = i;
for (int j = 19; j >= 0; --j)
if (dep[dp[tmp][j]] >= dep[to[i]]) { //再去1的路上 有一个可以买2^j次珠宝的路径
cnt += 1 << j; //购买次数
tmp = dp[tmp][j];
}
printf("%lld\n", cnt);
}
return 0;
} 
京公网安备 11010502036488号