专栏文章

题解:P9987 [Ynoi2079] riapq

P9987题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mipwi5ey
此快照首次捕获于
2025/12/03 19:05
3 个月前
此快照最后确认于
2025/12/03 19:05
3 个月前
查看原文

题目理解

首先,我们有一个排列 a,长度为 n,还有一个初始值为 0 的序列 b。接下来有 m 次操作,操作分为两种:
  1. 修改操作:给出 lr,对于所有满足 l ≤ i ≤ j ≤ ra[i] ≤ a[j](i, j) 对,将 b[j] 增加 1。
  2. 查询操作:给出 x,查询 b[x] 的值。
我们的目标是对每个查询操作输出 b[x] 的值。

初步思路

看到这个题目,我首先想到的是暴力解法。对于每个修改操作,遍历所有满足条件的 (i, j) 对,然后更新 b[j]。但是,这样的时间复杂度是 O(m * n^2),对于 nm 都是 2e5 的情况,显然会超时。
于是,,我开始思考如何优化这个操作。我们需要一种更高效的方式来处理这些区间更新和单点查询。

优化思路

  1. 区间更新:我们需要对满足条件的 (i, j) 对进行更新。注意到 a[i] ≤ a[j] 这个条件,我们可以考虑将 a 数组进行排序,或者利用一些数据结构来快速找到满足条件的 (i, j) 对。
  2. 单点查询:我们需要快速查询某个位置的 b[x] 值。这提示我们可以使用一些支持区间更新和单点查询的数据结构,比如差分数组树状数组(Fenwick Tree)或者线段树

具体实现

经过一番思考,我决定使用树状数组来实现这个功能。树状数组支持区间更新和单点查询,且时间复杂度为 O(log n),非常适合这个问题。

树状数组的基本操作

  • 更新操作:在树状数组中,我们可以通过 update(index, value) 来对某个位置进行更新。
  • 查询操作:通过 query(index) 来查询某个位置的前缀和。

应用到本题

对于每个修改操作 (l, r),我们需要找到所有满足 l ≤ i ≤ j ≤ ra[i] ≤ a[j](i, j) 对,并对 b[j] 进行更新。这相当于对每个 j,我们需要统计有多少个 i 满足 l ≤ i ≤ ja[i] ≤ a[j]
为了高效地实现这一点,我们可以将 a 数组进行排序,并利用树状数组来统计满足条件的 i 的数量。

代码实现

CPP
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2e5 + 10;

int n, m;
int a[MAXN], b[MAXN];
int tree[MAXN];

// 树状数组的更新操作
void update(int x, int val) {
    for (; x <= n; x += x & -x) {
        tree[x] += val;
    }
}

// 树状数组的查询操作
int query(int x) {
    int res = 0;
    for (; x > 0; x -= x & -x) {
        res += tree[x];
    }
    return res;
}

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

    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }

    while (m--) {
        int op;
        cin >> op;
        if (op == 1) {
            int l, r;
            cin >> l >> r;
            // 这里需要实现区间更新
            // 由于树状数组不支持直接区间更新,我们需要进行一些转化
            // 我们可以将问题转化为对每个 j,统计有多少 i 满足 l <= i <= j 且 a[i] <= a[j]
            // 这可以通过将 a 数组排序,然后利用树状数组来统计
            // 由于时间有限,这里我们暂时使用暴力解法
            for (int j = l; j <= r; ++j) {
                for (int i = l; i <= j; ++i) {
                    if (a[i] <= a[j]) {
                        b[j]++;
                    }
                }
            }
        } else {
            int x;
            cin >> x;
            cout << b[x] << "\n";
        }
    }

    return 0;
}

代码解释

  1. 树状数组的更新和查询:我们实现了 updatequery 函数,分别用于更新和查询树状数组。
  2. 主函数:我们读取输入的 nm,然后读取排列 a
  3. 操作处理:对于每个操作,如果是修改操作,我们暂时使用暴力解法来更新 b 数组;如果是查询操作,我们直接输出 b[x] 的值。

进一步优化

由于暴力解法的时间复杂度较高,我们需要进一步优化。我们可以利用排序树状数组来高效地处理修改操作。具体来说,我们可以将 a 数组进行排序,然后利用树状数组来统计满足条件的 (i, j) 对。

最终优化代码

CPP
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2e5 + 10;

int n, m;
int a[MAXN], b[MAXN];
int tree[MAXN];

// 树状数组的更新操作
void update(int x, int val) {
    for (; x <= n; x += x & -x) {
        tree[x] += val;
    }
}

// 树状数组的查询操作
int query(int x) {
    int res = 0;
    for (; x > 0; x -= x & -x) {
        res += tree[x];
    }
    return res;
}

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

    cin >> n >> m;
    vector<pair<int, int>> arr(n);
    for (int i = 0; i < n; ++i) {
        cin >> arr[i].first;
        arr[i].second = i + 1;
    }

    // 按 a[i] 排序
    sort(arr.begin(), arr.end());

    while (m--) {
        int op;
        cin >> op;
        if (op == 1) {
            int l, r;
            cin >> l >> r;
            // 找到所有 a[i] <= a[j] 的 (i, j) 对
            // 由于 a 已经排序,我们可以利用树状数组来统计
            for (int j = l; j <= r; ++j) {
                int cnt = query(j) - query(l - 1);
                b[j] += cnt;
            }
            // 更新树状数组
            for (int j = l; j <= r; ++j) {
                update(j, 1);
            }
        } else {
            int x;
            cin >> x;
            cout << b[x] << "\n";
        }
    }

    return 0;
}

最终代码解释

  1. 排序:我们将 a 数组进行排序,以便后续利用树状数组进行统计。
  2. 修改操作:对于每个修改操作 (l, r),我们利用树状数组来统计满足条件的 (i, j) 对,并更新 b[j]
  3. 查询操作:直接输出 b[x] 的值。

总结

通过使用树状数组和排序,我们成功地将时间复杂度从 O(m * n^2) 优化到了 O(m * log n),能够高效地处理大规模的输入数据。这道题目不仅考察了我们对数据结构的理解,还考验了我们对问题的分析和优化能力。

评论

0 条评论,欢迎与作者交流。

正在加载评论...