【每日一题】5. 城市网络 (树上倍增)

补题链接: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;
}
posted @ 2021-04-10 21:29  RioTian  阅读(100)  评论(0编辑  收藏  举报