专栏文章
题解: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 次操作,操作分为两种:- 修改操作:给出
l和r,对于所有满足l ≤ i ≤ j ≤ r且a[i] ≤ a[j]的(i, j)对,将b[j]增加 1。 - 查询操作:给出
x,查询b[x]的值。
我们的目标是对每个查询操作输出
b[x] 的值。初步思路
(i, j) 对,然后更新 b[j]。但是,这样的时间复杂度是 O(m * n^2),对于 n 和 m 都是 2e5 的情况,显然会超时。优化思路
-
区间更新:我们需要对满足条件的
(i, j)对进行更新。注意到a[i] ≤ a[j]这个条件,我们可以考虑将a数组进行排序,或者利用一些数据结构来快速找到满足条件的(i, j)对。 -
单点查询:我们需要快速查询某个位置的
b[x]值。这提示我们可以使用一些支持区间更新和单点查询的数据结构,比如差分数组、树状数组(Fenwick Tree)或者线段树。
具体实现
经过一番思考,我决定使用树状数组来实现这个功能。树状数组支持区间更新和单点查询,且时间复杂度为 O(log n),非常适合这个问题。
树状数组的基本操作
- 更新操作:在树状数组中,我们可以通过
update(index, value)来对某个位置进行更新。 - 查询操作:通过
query(index)来查询某个位置的前缀和。
应用到本题
对于每个修改操作
(l, r),我们需要找到所有满足 l ≤ i ≤ j ≤ r 且 a[i] ≤ a[j] 的 (i, j) 对,并对 b[j] 进行更新。这相当于对每个 j,我们需要统计有多少个 i 满足 l ≤ i ≤ j 且 a[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;
}
代码解释
- 树状数组的更新和查询:我们实现了
update和query函数,分别用于更新和查询树状数组。 - 主函数:我们读取输入的
n和m,然后读取排列a。 - 操作处理:对于每个操作,如果是修改操作,我们暂时使用暴力解法来更新
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;
}
最终代码解释
- 排序:我们将
a数组进行排序,以便后续利用树状数组进行统计。 - 修改操作:对于每个修改操作
(l, r),我们利用树状数组来统计满足条件的(i, j)对,并更新b[j]。 - 查询操作:直接输出
b[x]的值。
总结
通过使用树状数组和排序,我们成功地将时间复杂度从 O(m * n^2) 优化到了 O(m * log n),能够高效地处理大规模的输入数据。这道题目不仅考察了我们对数据结构的理解,还考验了我们对问题的分析和优化能力。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...