所谓离散化就是当我们需要存一定数量的数组时,数组开不了那么大,但是数字的种数不多,故离散化就是改变了数据数量的相对大小
给定一个包含大量重复元素数组,而我们用到其中的每一个数即可,去重显然是更好的选择
unique函数
头文件:#include
注意:使用前一定要先将无序的列表排序,使重复的元素相邻
原理:
举例:
int数组a[7](排过序的):1,1,2,3,3,3,6
使用过unique(a,a+7),数组变为1,2,3,6,3,3,6
(PS:unique只改变了前4(即不重复元素的个数)个数字,后边的不变)
而且返回的就是上面标记数字的地址
因此常常用来求出不重复元素:
int a[n];
sort(a,a+n);
int num=unique(a,a+n)-a; //按上面例子来看num就是4
for(int i=0;i<num;i++)
cout<<a[i]<<endl;
为什么给它起名映射离散化呢,因为这种离散化不减少元素的个数,而且是采用给数组每个数映射一个id的方法间接得到我们需要的结果。例如一个只有四个数的数组a:
a[i] = 104,1e6,1e8,1e10
而如果我们给每个数一个映射将a数组转化为b[i] = 1,2,3,4,我们可以发现数组元素之间的相对大小关系是不影响的
还有如果我们要得到一堆数据中每个数i(i很大)作为数组下标的值,而这些数据总数却不太多。显然直接开数组是存不下的,因此就需要离散化,下面举一个很实用的例子
逆序对
对于一个数组a若 i > j && a[i] < a[j] 那么称a[i]与a[j]是一个逆序对
其实逆序对就是求每个数前面有多少个比它大的数。暴力求解显然超时,那么我们这样想,如果给定一组乱序数,求解每个数前面有多少比它小的,那么可以用树状数组。树状数组就是可以很方便维护前缀和,从而得到每个数前面有多少比它小的,但是我们如果用数组存每个数作为下标,开不了很大的数组怎么办?离散化!
究竟如何离散化,我们先给每个数按输入顺序分配一个id,可以用结构体保存,例如
id | 1 | 2 | 3 | 4 |
---|---|---|---|---|
a[i] | -1 | 19 | 2 | 5 |
那么我们按a[i]从小到大排序可以得到
id | 1 | 3 | 4 | 2 |
---|---|---|---|---|
a[i] | -1 | 2 | 5 | 19 |
那么我们可以发现原始数组的逆序对数是2,而按id来看得到的逆序对个数也为2。于是我们只要按id作为下标存到树状数组中,我们每次查询得到某个id前面有多少个比它小的,那么id-getSum(a[i].id)就可以得到前面有多少个比它大的,但是这样显然太麻烦,我们如果直接把a[i]按id从大到小排序,查询每个id前面都多少个比它大的不就行了?但是这时要注意先更新和先查询的区别,如果先更新的话,该节点本身以及之前的树状数组对应值都会更新,因此要getSum(a[i].id-1)。但是如果先查询再更新的话就直接getSum(a[i].id)即可
洛谷逆序对参考代码:
#include <iostream>
#include <algorithm>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
const int maxn=5e5+10;
struct node{
int id,v;
}a[maxn];
int t[maxn];
ll n;
bool cmp(node &a,node &b){
if(a.v==b.v) return a.id>b.id;
return a.v>b.v;
}
void update(int i){
while(i<=n){
t[i]++;
i+=lowbit(i);
}
}
ll getSum(int i){
ll ans=0;
for(;i;i-=lowbit(i))
ans+=t[i];
return ans;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i].v);
a[i].id=i;
}
sort(a+1,a+1+n,cmp);
ll ans=0;
for(int i=1;i<=n;i++){
ans+=getSum(a[i].id); //先查询的话直接查询每个id即可
update(a[i].id);
}
printf("%lld\n",ans);
return 0;
}