城市网络(3月31日题目 树上倍增 ST表)

https://ac.nowcoder.com/discuss/395376
https://ac.nowcoder.com/acm/problem/13331

ST表

倍增的最基础应用应该是ST表。ST表本质是动态规划,主要用于解决RMQ(Range Minimum/Maximum Query, 即区间最值查询) 问题,是一种离线算法

以求区间最大值为例,它的基本思想是:用 f [ i ] [ j ] f[i][j] f[i][j]维护从从i开始长度为 2 j 2^{j} 2j的区间的最大值,例如 f [ 1 ] [ 1 ] {f[1] [1]} f[1][1]表示的是以1开始长度为2的区间的最大值, f [ 1 ] [ 2 ] {f[1][2]} f[1][2]表达的是以1开始长度为4的区间的最大值。我们可以看到 j {j} j每增加1,区间的长度乘以二,于是两个相邻区间合并就可以得到当前这个长度的区间的最大值了,也就是说 f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) f[i] [j] = max(f[i] [j-1], f[i+2^{j-1}] [j-1]) f[i][j]=max(f[i][j1],f[i+2j1][j1]),这样 O ( n l o g n ) {O(nlogn)} O(nlogn)的时间复杂度我们就可以维护出 f f f数组。
求区间 [ L , R ] [L,R] [L,R]的最大值,只要求出两个刚好大于 ( R − L ) / 2 (R-L)/2 (RL)/2的区间(一个左边界为 L L L,一个右边界为 R R R)就好了,

伪代码参考:

void RMQ_ST(int n) //预处理ST表,数组中有n个元素
{
    for (int i = 1; i <= n; i++)
        f[i][0] = a[i];
    int m = (int)(log((double)n) / log(2.0));
    for (int j = 1; j <= m; j++)              //先循环j再循环i,因为必须要把长度为1的区间都算出来了才能求长度为2的
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
            f[i][j] = min(f[i][j - 1], f[i + (1 << j)][j - 1]);
}
int RMQ_find(int l, int r) //求区间 l 到 r 之间的最值
{
    int k = (int)(log(1.0 * (r - l + 1)) / log(2.0)); //我们用两个长度刚好大于(r-l+1)/2的区间拼起来
    return min(f[l][k], f[r - (1 << k) + 1][k]);
}

题目描述

有一个树状的城市网络(即 n n n 个城市由 n − 1 n-1 n1 条道路连接的连通图),首都为 1 号城市,每个城市售卖价值为 a i a_i ai 的珠宝。
你是一个珠宝商,现在安排有 q q q 次行程,每次行程为从 u u u 号城市前往 v v v 号城市(走最短路径),保证 v v v u u u 前往首都的最短路径上
在每次行程开始时,你手上有价值为 c c c 的珠宝(每次行程可能不同),并且每经过一个城市时(包括 u u u v v v ),假如那个城市中售卖的珠宝比你现在手上的每一种珠宝都要优秀(价值更高,即严格大于),那么你就会选择购入。
现在你想要对每一次行程,求出会进行多少次购买事件。

输入描述:
第一行,两个正整数 n , q ( 2 ≤ n ≤ 1 0 5 , 1 ≤ q ≤ 1 0 5 ) n , q (2 ≤ n ≤ 10^5 , 1 ≤ q ≤ 10^5) n,q(2n105,1q105)
第二行, n n n 个正整数 a i ( 1 ≤ a i ≤ 1 0 5 ) a_i (1 ≤ a_i ≤ 10^5) ai(1ai105) 描述每个城市售卖的珠宝的价值。
接下来 n − 1 n-1 n1 行,每行描述一条道路 x , y ( 1 ≤ x , y ≤ n ) x , y (1 ≤ x,y ≤ n) x,y(1x,yn),表示有一条连接 x x x y y y 的道路。
接下来 q q q 行,每行描述一次行程 u , v , c ( 1 ≤ u , v ≤ n , 1 ≤ c ≤ 1 0 5 ) u , v , c (1 ≤ u,v ≤ n , 1 ≤ c ≤ 10^5) u,v,c(1u,vn,1c105)

输出描述:
对于每次行程输出一行,为所购买次数。

input:
5 4
3 5 1 2 4
1 2
1 3
2 4
3 5
4 2 1
4 2 2
4 2 3
5 1 5

output:
2
1
1
0

树上倍增

本题用的是树上倍增。这个题难了我两天😭,终于看懂了。
题目描述中的黄色的两句是关键,看见了这两句就能知道用树上倍增了。
f [ i ] [ j ] {f[i] [j]} f[i][j] 表示从 i i i 往上走,能买到的第 2 j 2^j 2j 个珠宝点是哪个点,显然,如果我们知道每个 f [ i ] [ 0 ] {f[i] [0]} f[i][0] 的值,那么 f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] {f[i] [j] =f[f[i] [j-1]] [j-1]} f[i][j]=f[f[i][j1]][j1] ( i {i} i 往上的第 2 j − 1 2^{j-1} 2j1 个点再往上 2 j − 1 2^{j-1} 2j1 个点)。求 f [ i ] [ 0 ] f[i][0] f[i][0] 用倍增法求解。查询开始时手上有价值 c c c 的珠宝怎么办?可以在每次查询的起点加一个权值为 c c c 的子节点作为起点。详解看上面给的链接

Code

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5; //因为查询要单独建立一个起点,所以要把q次查询加到总的点数中,1e5+1e5

vector<int> G[maxn]; //存图
int a[maxn];         //每个点的权值a_i
int f[maxn][20];     //f[i][j]表示从i往上走,能买到第2^j个珠宝的点是哪个,2^19=‭524288‬>2e5
int to[maxn];        //记录q次查询的终点
int deep[maxn];      //记录每个点的深度,根节点d[1]=0

void dfs(int cur, int fa)
{
    int x = fa;
    for (int i = 19; i >= 0; i--)
        if (f[x][i] && a[f[x][i]] <= a[cur]) //如果x往上跳2^k步这个点存在,且这个点的权值比a[cur]小,说明这个点以下的点都比a[cur]小,所以x就跳到这个点再往上跳
            x = f[x][i];

    if (a[x] > a[cur]) //判断比a[cur]大的点是x还是x上面那个点
        f[cur][0] = x;
    else
        f[cur][0] = f[x][0];

    for (int i = 1; i <= 19; i++)
        f[cur][i] = f[f[cur][i - 1]][i - 1]; //倍增求出每个点向上2^i步的点

    deep[cur] = deep[fa] + 1; //维护高度,查找是用来判断终点

    int sz = G[cur].size();
    for (int i = 0; i < sz; i++)
    {
        int v = G[cur][i];
        if (v != fa)
            dfs(v, cur); //搜儿子
    }
}

int main()
{
#ifdef LOCAL_LIUZHIHAN
    freopen("in.in", "r", stdin);
    // freopen("out.out", "w", stdout);
#endif
    int n, q;
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    int x, y;
    for (int i = 1; i < n; i++)
    {
        scanf("%d%d", &x, &y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    for (int i = n + 1; i <= n + q; i++)
    {
        scanf("%d%d%d", &x, &y, &a[i]);
        G[i].push_back(x);
        G[x].push_back(i); //在u点的下方接一个权值为c的点作为起点
        to[i - n] = y;     //记录q次查询的终点
    }
    dfs(1, 0);
    for (int i = 1; i <= q; i++)
    {
        int ans = 0;
        x = i + n;                            //x是查询的起点的编号
        for (int k = 19; k >= 0; k--)         //从大到小枚举k
            if (deep[f[x][k]] >= deep[to[i]]) //如果跳完之后到达的点深度大于等于目标点,表示在u->v的最短路上,那么就跳到这个点继续往上跳
            {
                ans += (1 << k);
                x = f[x][k];
            }
        printf("%d\n", ans);
    }
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值