题意
给出一些区间,每个区间用l,r来标识。区间的端点各不相同。求出每个区间所包含其他区间的数量。
思路
参考了官方题解。
官方题解的这种讲述方法很有意义:用一维的方法来解决二维的问题。在这个问题里面,每个区间包含其他的区间,这涉及到各自区间的左端点之间的大小关系、右端点之间的大小关系,这是一个二维的问题。然而我们可以用排序控制左端点的大小关系,进而把它转化为只需要关心右端点的一维问题,这在许多问题中都是非常常见的办法。
具体来说,对于所有点对,我们按照左端点排序,然后以左端点的数值从大到小的顺序进行答案统计。这意味着,考虑当前区间包含的区间数目,只要看在所有已考虑过的右端点中,有多少右端点数值比当前区间的右端点数值小就可以了。这样我们其实就是在动态地维护一个前缀和,每次问询y值以前的前缀和是多少,每次把y值加1。维护这样的前缀和,用到了Fenwick Tree,树状数组。(记一下英文名,下次看题解的时候就不用再查了)。
当然,这里的x,y都是正负1e9的区间,但是最多只有2e5对值,所以我选择把右端点用map离散化。官方题解好像是把右端点排序后取出顺序,其实就是另外一种离散化的方法吧。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int max_n = 2e5 + 10;
map<int, int> mp;
pair<pair<int, int>, int> seg[max_n]; //第一维记录区间,第二维记录编号
vector<int> v;
int n;
int seed = 0;
int comp[max_n];
int ans[max_n];
int toans[max_n];
int ft[max_n]; //树状数组
int lowbit(int x) {
return x & -x;
}
void add(int pos, int add_num) {
//树状数组大小为n,建立在将所有右端点离散化之后的基础上,这样右端点的值为1到n
for (int i = pos; i <= n; i += lowbit(i)) {
ft[i] += add_num;
}
}
int ask(int pos) {
int ans = 0;
for (int i = pos; i > 0; i -= lowbit(i)) {
ans += ft[i];
}
return ans;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x, y;
scanf("%d %d", &x, &y);
seg[i] = make_pair(make_pair(x, y), i);
v.push_back(y);
}
//对所有y进行离散化
sort(v.begin(), v.end());
for (int i = 0; i < (int)v.size(); i++) {
mp[v[i]] = ++seed;
}
//从大到小遍历x,维护y值的前缀和
sort(seg + 1, seg + n + 1);
for (int i = n; i > 0; i--) {
// int x = mp[seg[i].first.first];
// cout << "x:" << x << endl;
int y = mp[seg[i].first.second],
id = seg[i].second;
// cout << "Y:" << y << endl;
ans[id] = ask(y);
add(y, 1);
}
for (int i = 1; i <= n; i++) {
printf("%d\n", ans[i]);
}
return 0;
}
总结
当时看到wcn学姐距离上一题只用了20分钟就过了这题,其实就想着这可能是个树状数组,因为学姐给我一种精通树状数组的感觉(强行乱感觉),当然主要原因还是,2e5的数量级,并且她过得这么快,想到相应的数据结构就可能是树状数组。
通过这道题,我对树状数组的理解加深了,更加理解了树状数组作为优秀的单点修改的工具,可以很好地维护前缀和。
并且“一维方法解决二维问题”参考性很强,算是了解了多一种套路。