题目传输门:~~~
题目大意
题意:给出数组 A,定义f(l, r, x)为 A 的下标 l 到 r 之间,等于x的元素个数。
问:满足 f(1, i, a[i]) > f(j, n, a[j]) 的(i,j)的对数是多少?
分析
我们令 Li = f(1, i, a[i]) , Rj = f(j, n, a[j]),问题就可以转化为:满足 Li > Ri,并且i < j 的(i,j)的对数。
这里引入一个概念:逆序对 知识点传送门
我们可以发现,题目可以转化为求 L 和 R 的逆序对的个数
比如题目中的样例:
n = 7,A = {1,2,1,1,2,2,1} 时,通过计算得出下表
A | 1 | 2 | 1 | 1 | 2 | 2 | 1 |
---|---|---|---|---|---|---|---|
L | 1 | 1 | 2 | 3 | 2 | 3 | 4 |
R | 4 | 3 | 3 | 2 | 2 | 1 | 1 |
我们从左到右扫描 L,并去查询满足 L 的逆序对的个数
当 i = 1 时,需要考虑的 R 在区间(2,7)没有 R 满足
当 i = 2 时,需要考虑的 R 在区间(3,7)没有 R 满足
当 i = 3 时,需要考虑的 R 在区间(4,7)满足的 R 有 R6 和 R7
当 i = 4 时,需要考虑的 R 在区间(5,7)满足的 R 有 R5 、R6 和 R7
当 i = 5 时,需要考虑的 R 在区间(6,7)满足的 R 有 R6 和 R7
当 i = 6 时,需要考虑的 R 在区间(7,7)没有 R 满足
当 i = 7 时,没可以考虑的 R
现在我们需要先把 L 和 R 给统计出来,由于题目数据范围比较大,我们通过map去统计L 和 R。统计出来的个数,就可以去求解了(也就是说,原来的数组A 的数据在题中的作用已经被 R 和 L 给代替,很明显 L 和 R 的数据范围要远小于 A)
这种数据转化的思想叫:离散化 知识点传送门
计算逆序对个数的高效的方法就是使用树状数组,时间复杂度为O(logn)。
树状数组: 知识点传送门
原理:
- 我们用树状数组去纪录所有 Ri 出现的次数(树状数组的下标为 Ri 的值,对应 Ri 出现的次数)。这时树状数组储存的是 (1,n)区间中,全部 Ri 出现的次数
- 我们定义一个指针 i ,从左到右去扫描 L。扫描时:
- 先对树状数组的[Ri] - 1,这时树状储存的是 (i+1,n)区间中,全部 Ri 出现的次数
- 再计算树状数组中(1,Li - 1)的和sum(Li - 1),也就是在区间(i+1,n)中全部小于 Li 的 R 的个数和
- res += sum(Li - 1)
当我们执行完第一步时:树状数组的样子应该为这样的
下表 1 2 3 4 值 2 2 2 1 执行到 i = 4 时,树状数组的样子为
下表 1 2 3 4 值 2 2 0 0 这时 R4 = 2,我们在数组数组下表为 2 的位置减一
下表 1 2 3 4 值 2 1 0 0 这时 sum(L4 - 1) = sum(2) = 2 + 1 = 3
sum(2) 就是这次扫描对答案的贡献
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e6+10;
int tree[maxn] = {0}; // 用来存树状数组
int v[maxn] = {0};
int n;
int lowbit(int x){
return x & (-x);
}
void update(int x, int data){ // 单点修改 在下表为 x 的位置加 data
while(x <= n){ // ?
tree[x] += data;
x += lowbit(x);
}
}
ll sum(int x){ // 计算下标 1 到 m 的和
ll res = 0;
while(x > 0){
res += tree[x];
x -= lowbit(x);
}
return res;
}
int main(){
ios::sync_with_stdio(false); // 如果用cin 输入必须加这一句不然会 TIL 17
cin >> n;
map<int, int> x; // 纪录 R
for(int i = 1; i <= n; i++){
cin >> v[i];
x[v[i]]++; // 纪录 v[i] 对 R 的贡献
update(x[v[i]], 1); // 把这次 v[i] 的出现加到树状数组中
}
ll res = 0;
map<int, int> y; // 纪录 L
for(int i = 1; i <= n; i++){
x[v[i]]--; // 减去 v[i] 对 R 的贡献
add(x[v[i]], -1); // 减去指针扫描处V[i]出现的一次,更正树状数组
y[v[i]]++; // 纪录 v[i] 对 L 的贡献
res += sum(y[v[i]] - 1); // 更正答案
}
cout << res << endl;
return 0;
}
第一次接触离散化和逆序对,写的不好的地方和讲的不清楚的地方,欢迎留言指出 (●’◡’●)