• 【每日一题】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;
    }
    

    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    ListCtr 每一行都加上选择框
    VC中MFC check box的用法
    第二章 掌握C++(2)C++的特性(上)
    第二章 掌握C++(1)从结构到类
    道路横断面设计
    第一章 Windows程序内部运行机制(5)动手编写一个Windows程序
    第一章 Windows程序内部运行机制(4)WinMain函数(续)
    将div旋转任意角度
    地址栏图标修改
    script 错误
  • 原文地址:https://www.cnblogs.com/RioTian/p/14642180.html
Copyright © 2020-2023  润新知