离散化入门及使用方法与场景

离散化概念:

当我们只关心数据的大小关系时,则可以用离散化将数据缩小。用排名代替原数据进行处理的一种预处理方法。离散化本质上是一种哈希,它在保持原序列大小关系的前提下把其映射成正整数。当原数据很大或含有负数、小数时,难以表示为数组下标,一些算法和数据结构(如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(Nlog⁡N) ,所以离散化的复杂度也是 O(Nlog⁡N) 。完整代码很短:

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<int>()就可以了。

不去重:

例如,现在我们有序列A=[10, 23, 35, 3, -40, 3]。我们先复制一个同样的序列:

若求其逆序对个数的话:

则降序排序为C=[3, 2, 1, 6, 4, 5]。这里6不会为5做出贡献,故两个相同的元素不会影响逆序对个数,故在这儿是后来居上才能使其相同元素互不影响,不同场景各不相同!求和需要用树状数组进行压缩,思考到add的板子(上章提到),故在这儿我选用降序排序,先出现的为后出现的比自己小的贡献1!

思考:如果求正序对呢?或者通过升序求逆序对可以不呢?

下面是求逆序对个数题代码:

#include<bits/stdc++.h>
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<<ans<<'\n';
}
//-----------------------------------------------------------------------------------------
int main()
{
	//ll t = 1 ;
	//cin >> t ;
	//for(ll i = 1 ; i <= t ; i++){
		// cout << "Case #" << i << ": "; 
		solve();
		//cout<<'\n';
	//}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值