RMQ算法模板加应用(一道题复习RMQ打表、二分、dfs序、前缀和)

模板题

从前有个人名叫 WNB,他有着天才般的记忆力,他珍藏了许多许多的宝藏。

在他离世之后留给后人一个难题(专门考验记忆力的啊!),如果谁能轻松回答出这个问题,便可以继承他的宝藏。

题目是这样的:给你一大串数字(编号为 1 到N,大小可不一定哦!),在你看过一遍之后,它便消失在你面前,随后问题就出现了,给你 M 个询问,每次询问就给你两个数字A,B,要求你瞬间就说出属于 A 到 B 这段区间内的最大数。

一天,一位美丽的姐姐从天上飞过,看到这个问题,感到很有意思(主要是据说那个宝藏里面藏着一种美容水,喝了可以让这美丽的姐姐更加迷人),于是她就竭尽全力想解决这个问题。

但是,她每次都以失败告终,因为这数字的个数是在太多了!

于是她请天才的你帮他解决。如果你帮她解决了这个问题,可是会得到很多甜头的哦!

输入格式

第一行一个整数 N 表示数字的个数。

接下来一行为 N 个数,表示数字序列。

第三行读入一个 M,表示你看完那串数后需要被提问的次数。

接下来 M 行,每行都有两个整数 A,B。

输出格式

输出共 M 行,每行输出一个数,表示对一个问题的回答。

数据范围

1≤N≤2×10^5,
1≤M≤10^4,
1≤A≤B≤N。

输入样例:

6
34 1 8 123 3 2
4
1 2
1 5
3 4
2 3

输出样例:

34
123
123
8

代码 

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2e5 + 10, M = 18;
int dp[N][M], w[N];

int n, m;
int A, B;

void init()
{
    for(int j = 0;j < M;j ++){
        for(int i = 1;i + (1 << j) - 1 <= n;i ++){
            if(!j) dp[i][j] = w[i];
            else dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
        }
    }
}

void query(int A, int B)
{
    int len = B - A + 1;
    int k = log(len) / log(2);
    cout << max(dp[A][k], dp[B - (1 << k) + 1][k]) << endl;
}

int main()
{
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> w[i];
    
    init();
    
    cin >> m;
    while(m --){
        cin >> A >> B;
        query(A, B);
    }
    
    return 0;
}

应用 

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

小Z生活在树上,这一天他计划将他的住所向下迁移。
现在给出一颗有 n 个结点且以 1 结点为根的树,树上的每一条边都有一个权值。有q 个询问,每一次询问给出两个数 u 和 d,其中 u 表示小Z目前居住的结点,d 表示小Z计划向下移动的次数。小Z希望向下移动的路径上的边的权值和最大,请你输出能得到的最大权值。如果没有合法路径,输出 −1。

输入描述:

第一行包含一个整数 n(1≦n≦105),表示树的结点数。

接下来的 n−1n-1n−1 行,每行包含三个整数 a,b,w(1≦a,b≦n;1≦w≦109),表示结点 a 和结点 b 之间有一条权值为 w 的边 。

接下来一行输入一个整数 q(1≦q≦105),表示询问次数。

接下来的 q 行,每行包含两个整数 u,d(1≦u≦n;1≦d≦n),表示小Z目前居住的结点和计划向下移动的次数。

输出描述:

对于每一个询问,输出小Z能够得到的最大权值和。如果没有合法路径,输出 −1。

示例1

输入

5
1 2 3
1 3 5
2 4 2
2 5 1
3
1 2
2 1
3 2

输出

5
2
-1

说明

第一个查询中,从结点 1 向下移动2 次,路径为  41→2→4,权值和为3 + 2 = 53+2=5。
第二个查询中,从结点 2 向下移动 1 次,路径为  42→4,权值和为 2。
第三个查询中,从结点 3 向下移动 2 次没有合法路径,输出 −1。

相当难的一道题,用到的算法:dfs序,RMQ打表, 前缀和,二分。而且很卡空间,前面的算法不仅要掌握还要非常非常熟练,不然就算想到思路了,代码也写不出来

首先就是要知道dfs序有什么用, 比如给定树上两个点 u, v 如果要判断 v 是否是 u 的子节点,那么dfs序要满足 lu < lv < rv < ru。用 dep[u] + d 表示跳到的层,如果超出了直接 -1 即可, 没超出就二分找子节点,然后用RMQ预处理好的表去查询即可

代码 

#include <iostream>
#include <cmath>
#include <cstring>
#include <vector>
#include <algorithm>

#define ll long long

using namespace std;

typedef pair<int, int> PII;

const int N = 1e5 + 10;
int dep[N], dfn[N], l[N], r[N];
ll val[N];

void solve()
{
    int n;
    cin >> n;
    vector<vector<PII>> e(n + 1);
    for (int i = 1, u, v, w; i < n; i++) {
        cin >> u >> v >> w;
        e[u].push_back({v, w});
        e[v].push_back({u, w});
    }

    vector<vector<int>> p(n + 1);
    int tot = 0;
    dep[1] = 1;
    auto dfs = [&](auto &&self, int u, int fa) -> void {
        dfn[u] = ++tot;
        l[u] = tot;
        for (auto [v, w] : e[u]) {
            if (v == fa) continue;
            dep[v] = dep[u] + 1;
            p[dep[v]].push_back(v);
            val[v] = val[u] + w;
            self(self, v, u);
        }
        r[u] = tot;
    };

    auto cmp = [&](int A, int B) -> bool {
        return dfn[A] < dfn[B];
    };
    dfs(dfs, 1, 0);

    for (int i = 1; i <= n; i++) {
        sort(p[i].begin(), p[i].end(), cmp);
    }

    vector<vector<vector<ll>>> dp(n + 1);
    for (int k = 1; k <= n; k++) {
        if (p[k].size() == 0) continue;
        dp[k].resize(p[k].size() + 1);
        int len = log2(p[k].size()) + 1;
        for (int z = 0; z < p[k].size(); z++) {
            dp[k][z].resize(len);
            dp[k][z][0] = val[p[k][z]];
        }
        for (int j = 1; j < len; j++) {
            for (int i = 0; i < p[k].size() && i + (1 << j) - 1 < p[k].size(); i++) {
                dp[k][i][j] = max(dp[k][i][j - 1], dp[k][i + (1 << (j - 1))][j - 1]);
            }
        }
    }

    // 查询区间最大值
    auto ask = [&](int l, int r, int u, int deep) -> ll {
        int len = r - l + 1;
        int k = log2(len);
        return max(dp[deep][l][k], dp[deep][r - (1 << k) + 1][k]);
    };

    int m;
    cin >> m;
    while (m--) {
        int u, d;
        cin >> u >> d;
        int nd = dep[u] + d;  // 目标深度

        if (nd > n || p[nd].size() == 0) {
            cout << "-1\n";
            continue;
        }

        int l1 = 0, r1 = p[nd].size() - 1;
        int ansl = -1, ansr = -1;
        while (l1 < r1) {
            int mid = (l1 + r1) >> 1;
            if (dfn[p[nd][mid]] >= l[u]) r1 = mid;
            else l1 = mid + 1;
        }

        if (dfn[p[nd][l1]] >= l[u]) ansl = l1;

        if (ansl == -1) {
            cout << "-1\n";
            continue;
        }

        int l2 = 0, r2 = p[nd].size() - 1;
        while (l2 < r2) {
            int mid = (l2 + r2 + 1) >> 1;
            if (dfn[p[nd][mid]] <= r[u]) l2 = mid;
            else r2 = mid - 1;
        }

        if (dfn[p[nd][l2]] <= r[u]) ansr = l2;

        if (ansr == -1 || ansl > ansr) {
            cout << "-1\n";
            continue;
        }
        cout << ansl << " " << ansr << endl;
        cout << ask(ansl, ansr, u, nd) - val[u] << endl;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    solve();

    return 0;
}

加油

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值