当我们只关心数据的大小关系时,则可以用离散化将数据缩小。用排名代替原数据进行处理的一种预处理方法。离散化本质上是一种哈希,它在保持原序列大小关系的前提下把其映射成正整数。当原数据很大或含有负数、小数时,难以表示为数组下标,一些算法和数据结构(如BIT)无法运作,这时我们就可以考虑将其离散化。
这很好理解,但是当序列中出现重复的数时会比较麻烦,常考虑到是否去重,去重会改变原数组长度(理论上),不去重则必须考虑重复元素互不影响!下面介绍这两种思路的一些细节!
例如,现在我们有序列A=[10, 23, 35, 3, -40, 3]。我们先复制一个同样的序列:
ll C[MAXN];
memcpy(C, A, sizeof(A));
排序,去重:
sort(C, C + n);
ll len = unique(C, C + n) - C; // len为不重复元素的数量
std::unique()的返回值是一个迭代器(对于数组来说就是指针了),它表示去重后容器中不重复序列的最后一个元素的下一个元素。所以可以这样作差求得不重复元素的数量。现在我们有C=[-40, 3, 10, 23, 35]。
再用一个数组,储存A中每个元素在C中的排名:
ll L[MAXN];
for (ll i = 0; i < n; ++i)
L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 二分查找
这样我们就实现了原序列的离散化。得到L=[3, 4, 5, 2, 1, 2]。
因为排序和n次二分查找的复杂度都是 O(NlogN) ,所以离散化的复杂度也是 O(NlogN) 。完整代码很短:
ll C[MAXN], L[MAXN];
// 在main函数中...
memcpy(C, A, sizeof(A)); // 复制
sort(C, C + n); // 排序
ll l = unique(C, C + n) - C; // 去重
for (ll i = 0; i < n; ++i)
L[i] = lower_bound(C, C + l, A[i]) - C + 1; // 查找
离散化也不一定要从小到大排序,有时候也需要从大到小。这时在排序和查找时相应地加上greater
就可以了。
例如,现在我们有序列A=[10, 23, 35, 3, -40, 3]。我们先复制一个同样的序列:
若求其逆序对个数的话:
则降序排序为C=[3, 2, 1, 6, 4, 5]。这里6不会为5做出贡献,故两个相同的元素不会影响逆序对个数,故在这儿是后来居上才能使其相同元素互不影响,不同场景各不相同!求和需要用树状数组进行压缩,思考到add的板子(上章提到),故在这儿我选用降序排序,先出现的为后出现的比自己小的贡献1!
思考:如果求正序对呢?或者通过升序求逆序对可以不呢?
下面是求逆序对个数题代码:
#include
using namespace std;
using ll = long long;
ll n;
ll a[500005],c[500005],cnt[500005];
void add(ll x,ll k)
{
for(ll i=x;i<=n;i+=i&-i)
{
cnt[i]+=k;
}
}
ll query(ll x)
{
ll sum=0;
for(ll i=x;i;i-=i&-i)
{
sum+=cnt[i];
}
return sum;
}
ll cmp(ll x,ll y)
{
if(a[x]==a[y]) return x>y;
else return a[x]>a[y];//相同数字后出现在前面
}
void solve()
{
cin>>n;
ll ans=0;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
c[i]=i;
}
sort(c+1,c+1+n,cmp);
//5 7 2 5 2
//变为
//2 4 1 5 3
//2为4,5,3贡献1,4为5贡献1,1为5,3贡献1,5不贡献,3不贡献
//和为6
for(ll i=1;i<=n;i++)
{add(c[i],1);
ans+=query(c[i]-1);
//add(c[i],1);
}
/*树状数组作用:
用来求和运算,避免TLE
*/
cout<> t ;
//for(ll i = 1 ; i <= t ; i++){
// cout << "Case #" << i << ": ";
solve();
//cout<<'\n';
//}
}