三角洲高手的超能力 题目解析

题目

题解

这边只展开讲解 Hard 版本,题目将给定一个长度为 $n$ 的数组 $a$,并给定 $k$ 次询问,每次询问给定一个值 $d$ 要求我们回答 $a$ 中有多少个元素严格大于 $d$,有多少个元素严格小于 $d$。

首先我们观察题目的数据范围 $n$ 的最大值可达 $5 \times 10^6$,显然这个数据范围是不想要我们使用二分 / 排序解决这道题的,因为排序或者二分的时间复杂度均为 $O(N\log N)$ 显然在该题的极端数据下会超时(但是实际上数据并不会达到极端值,因为洛谷不允许上传超过 50 Mb 的数据包),如果有人想用暴力的方法直接在每次查询时遍历数组,显然更无法在该题的时间限制下通过本题,这个方法的时间复杂度为 $O(N \times K)$,$k$ 的最大值为 $5 \times 10^6$,显然会大幅度超过 $10^8$ 次循环(相当于 1s)。

因此这里给大家介绍一种数据结构,即 ‘前缀和’,前缀和是一种以 $O(1)$ 的时间复杂度查询数据,以 $O(N)$ 的时间复杂度构建且不支持修改数据的数据结构(若要修改数据,则必须重新构建),显然这道题不需要修改数据,后续 $k$ 次询问均为查询数据,假设一个场景(大家注意下标范围,这里不会对下标范围进行解释,大家自行理解):现在有一个长度为 $n$ 的数组 $b$,我们需要快速统计 $b$ 的前 $i$ 个元素的和,中途不会修改 $b$ 数组的元素。前缀和就是最适合这种场景的数据结构,具体来说我们声明一个数组 $c$,对于任何下标 $x$ 我们需要让 $c[x]$ 的值等于 $\sum^{x}_{i = 0}{b_i}$,实现方法很简单,我们只需要注意到一个递推关系:$c[i + 1] = c[i] + b[i + 1]$,那么显然我们只需要根据这个递推关系就能以 $O(N)$(循环 $n$ 次) 的时间复杂度成功构建前缀和数组,这里容易出现越界问题,我们可以同时让二者的长度增加一个,空出来的元素令为 $0$ 就可以自动处理越界问题了。

c[0] = 0;
for (int i = 0; i <= n - 2; ++i)
{
    c[i + 1] = c[i] + b[i + 1];
}

那么具体如何使用 ‘前缀和’ 解决这道题呢?实际上题目里有一个隐式的求和关系,我们假设出一个新的数组 $cnt$,它和原数组 $a$ 的关系为 $cnt[i] = a$ 中值为 $i$ 的元素的个数,假设 $a = {4, 2, 2, 1}$ 那么 $cnt[0] = 0$, $cnt[1] = 1$, $cnt[2] = 2$, $cnt[3] = 0$, $cnt[4] = 1$,那么若给定值 $d$,$a$ 中严格小于 $d$ 的元素个数即为 $\sum_{i = 0}^{d – 1} {cnt_i}$,设 a 中元素的最大值为 $mx$,严格大于 $d$ 的元素个数即为 $\sum_{i = 0}^{mx}{cnt[i]} – \sum_{i = 0}^{d}{cnt[i]}$,这边再给大家提醒一个点,即 cnt 数组的长度应该开多大?由于我们的 cnt 实际储存的是 a 中可能出现的元素的值,因此 cnt 的长度应该由 $a_i$ 的值域决定,可以直接开 $10^7$ 大小,最好开 $10^7 + 1$ 大小,前缀和数组的长度与 cnt 数组的长度设为相同,前者需要你们在代码中小心处理潜在的越界风险,后者则更为方便,可以避免大多数越界问题。

在使用前缀和时非常容易出现越界问题,这里总结一个通用方法,我们提前分清两个名词对应的对象 ‘被前缀和数组’ 及 ‘前缀和数组’,假设被前缀和的数组题目给定长度为 $n$,那么我们实际将二者的长度开为 $n + 1$,并在进行递推时将前缀和数组处理到第 n 个元素为止。

最后再提一下 C++ 的快读快写,如果想使用 C++ 的输入输出解决本题,最好看一下。

ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);

有些题目(如本题)需要进行大量的 i/o(输入输出),C++ 默认的 cin 与 cout 在运行上会进行很多对于竞赛而言 没有必要 的操作,因此我们需要使用上述代码开启 快读快写 以防 i/o 消耗大量时间使得代码超时

注意:如果使用上述代码,则不应再将 cin / cout 和 printf / scanf 一起使用,必须只能选择其一使用


#include <bits/stdc++.h>

using i128 = __int128;
using ll = long long;
using ull = unsigned long long;
using de = double;
using ld = long double;
using namespace std;

const int MAXN = 1e7 + 1;

// 全局数组会自动初始化元素初始值为 0
int cnt[MAXN + 1];
int pref[MAXN + 1];

void solve()
{
    int n, k; cin >> n >> k;
    for (int i = 0; i < n; ++i)
    {
        int num; cin >> num;
        cnt[num]++;
    }

    // 处理到 MAXN 为止,如果采取递推 pref[i] = pref[i - 1] + cnt[i] 则 i 应该从 1 开始,处理到 MAXN (而非 MAXN - 1 为止)
    for (int i = 0; i < MAXN; ++i)
    {
        pref[i + 1] = pref[i] + cnt[i + 1];
    }


    while (k--)
    {
        int d; cin >> d;
        cout << pref[MAXN] - pref[d] << ' ' << pref[d - 1] << '\n';
    }
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int t = 1;
    // cin >> t;

    while (t--)
    {
        solve();
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇