📜  门| GATE CS 2021 |设置 2 |第 38 题(1)

📅  最后修改于: 2023-12-03 15:28:38.963000             🧑  作者: Mango

GATE CS 2021 - 设置 2 第 38 题

这道题目要求我们实现一个具有以下几个函数的数据结构:

  • void insert(int key) - 将给定键插入数据结构中;
  • void deleteKey(int key) - 从数据结构中删除给定键;
  • int getMinKey() - 获取数据结构中最小键的值;
  • int getMaxKey() - 获取数据结构中最大键的值;
  • int getSum(int l, int r) - 获取数据结构中键值在 [l, r] 范围内的键值和。
题目分析

本题要求实现的是一个支持插入、删除、查询最值、查询区间合的数据结构。我们可以根据这个性质,来尝试设计一个合适的数据结构。

首先考虑一个问题,对于一个已经排序好的数组,我们如何快速计算出区间合呢?我们可以使用前缀和的思想,这样时间复杂度就是 $O(1)$。但是对于不排序的数组,我们该怎么办呢?一种经典的做法是使用线段树。线段树能够很好地支持各种区间查询操作,因此我们可以考虑使用线段树存储所有的键。

另外一个问题是如何维护最小值和最大值。我们可以在每个节点中维护一个字段来表示当前子树中的最小值和最大值。每次插入或删除元素的时候,我们只需要更新相应的节点的最小值和最大值即可。

算法实现

本题可以使用 C++ 或 Java 语言实现。下面是 C++ 实现的示例代码。

#include <cstdio>
#include <algorithm>

const int MAXN = 1000000;

int n, m, a[MAXN];
struct Node {
    int min, max, sum;
} t[4 * MAXN];

void build_tree(int v, int tl, int tr) {
    if (tl == tr) {
        t[v].min = t[v].max = a[tl];
        t[v].sum = a[tl];
    } else {
        int tm = (tl + tr) / 2;
        build_tree(v * 2, tl, tm);
        build_tree(v * 2 + 1, tm + 1, tr);
        t[v].min = std::min(t[v * 2].min, t[v * 2 + 1].min);
        t[v].max = std::max(t[v * 2].max, t[v * 2 + 1].max);
        t[v].sum = t[v * 2].sum + t[v * 2 + 1].sum;
    }
}

void update(int v, int tl, int tr, int pos, int new_val) {
    if (tl == tr) {
        t[v].min = t[v].max = new_val;
        t[v].sum = new_val;
    } else {
        int tm = (tl + tr) / 2;
        if (pos <= tm) {
            update(v * 2, tl, tm, pos, new_val);
        } else {
            update(v * 2 + 1, tm + 1, tr, pos, new_val);
        }
        t[v].min = std::min(t[v * 2].min, t[v * 2 + 1].min);
        t[v].max = std::max(t[v * 2].max, t[v * 2 + 1].max);
        t[v].sum = t[v * 2].sum + t[v * 2 + 1].sum;
    }
}

void delete_key(int v, int tl, int tr, int pos) {
    if (tl == tr) {
        t[v].min = t[v].max = t[v].sum = 0;
    } else {
        int tm = (tl + tr) / 2;
        if (pos <= tm) {
            delete_key(v * 2, tl, tm, pos);
        } else {
            delete_key(v * 2 + 1, tm + 1, tr, pos);
        }
        t[v].min = std::min(t[v * 2].min, t[v * 2 + 1].min);
        t[v].max = std::max(t[v * 2].max, t[v * 2 + 1].max);
        t[v].sum = t[v * 2].sum + t[v * 2 + 1].sum;
    }
}

int get_min_key(int v, int tl, int tr, int l, int r) {
    if (l > r) {
        return MAXN;
    }
    if (l == tl && r == tr) {
        return t[v].min;
    }
    int tm = (tl + tr) / 2;
    return std::min(get_min_key(v * 2, tl, tm, l, std::min(r, tm)),
                    get_min_key(v * 2 + 1, tm + 1, tr, std::max(l, tm + 1), r));
}

int get_max_key(int v, int tl, int tr, int l, int r) {
    if (l > r) {
        return -1;
    }
    if (l == tl && r == tr) {
        return t[v].max;
    }
    int tm = (tl + tr) / 2;
    return std::max(get_max_key(v * 2, tl, tm, l, std::min(r, tm)),
                    get_max_key(v * 2 + 1, tm + 1, tr, std::max(l, tm + 1), r));
}

int get_sum(int v, int tl, int tr, int l, int r) {
    if (l > r) {
        return 0;
    }
    if (l == tl && r == tr) {
        return t[v].sum;
    }
    int tm = (tl + tr) / 2;
    return get_sum(v * 2, tl, tm, l, std::min(r, tm)) +
           get_sum(v * 2 + 1, tm + 1, tr, std::max(l, tm + 1), r);
}

这个算法的时间复杂度是 $O(n \log n)$,空间复杂度也是 $O(n \log n)$,其中 $n$ 是数据结构中的键的数量。由于每次操作都需要遍历整棵线段树,因此时间复杂度是 $O(\log n)$ 的。

总结

本题考察了数据结构的设计和实现能力,希望大家能够认真复习线段树的相关知识及实现细节,加强自己的代码能力。