CodeForces - 486C(离散化 + 逆序对 + 树状数组)

题目传输门:~~~

题目大意

题意:给出数组 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;
}
	

第一次接触离散化和逆序对,写的不好的地方和讲的不清楚的地方,欢迎留言指出 (●’◡’●)

猜你喜欢

转载自blog.csdn.net/mldl_/article/details/106125623