康拓展开即逆康拓展开(简单易懂版)

康拓展开

当我们去搜康托展开这个关键字的时候,映入眼帘的是下面的一大堆公式:

X=a_{n}*(n-1)!+a_{n-1}*(n-2)!+...+a_{1}*0!

其中X为康拓展开值

a_{i}为整数,且0\leqslant a_{i}\leqslant i,1\leqslant i \leqslant n

a_{i}表示袁数在当前未出现的元素是排第几个。

很不错,这样一下子会把人弄得搞陀不清(湖南方言)。

所以,当我们学习一个算法的时候,首当其冲的应该是要知道此算法到底是做什么用的?

百度百科是这样解释的:康托展开是一个全排列到一个自然数双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

这是什么意思呢?

简单的来说就是将一个全排列的序列压缩成一个数。

例如,当我们定义全排列1 2 3 4 5 的序列为第一个序列的时候,那么1 2 3 5 4 则是第二个序列。所以,我们把序列1 2 3 4 5 的康拓展开值定义为X=1; 同理, 序列 1 2 3 5 4 的康拓展开值为X=2;

这样做的目的是:在使用哈希表的时候能够压缩空间并且避免哈希冲突。

具体来说操作时什么?

例如:已知3 1 4 2 5 序列,求其康拓展开值X。

我们手动模拟一下上面公式的具体操作

a_{5}=2,因为此时元素3在当前未出现的元素中排第二个,未出现元素即变为【1 2 4 5】(注意,这里序列排序是从0开始的);

a_{4}=0,因为此时元素1在当前未出现的元素中排第零个,未出现元素即变为【2 4 5】(同上)

a_{3}=1,因为此时元素4在当前未出现的元素中排第一个,未出现元素即变为【2 5】(同上);

a_{2}=0,因为此时元素2在当前未出现的元素中排第零个,未出现元素即变为【5】(同上);

a_{1}=0,因为此时元素5在当前未出现的元素中排第零个,未出现元素即变为【】(同上);

所以依据公式X=2*4!+0*3!+1*2!+0*1!+0*0!=50

所以,序列3 1 4 2 5 在(1 2 3 4 5) 5个全排列数中排51位。

思考:为什么序列3 1 4 2 5 在(1 2 3 4 5) 5个全排列中不排50位而是51位?

逆康拓展开

 所谓的逆康拓展开,就是给出康拓展开值X,求出序列。

我们给出X=50

则:

 

所以,原序列为a_{5}a_{4} a_{3} a_{2} a_{1}=3 1 4 2 5

现在,我们可以做一下题目了。

康拓展开

我们在计算康拓展开的值的时求a_{i}的时候,其实可以认为以下标为i的数为第一个元素,以下标为j(j>i)的数为第二个元素,求逆序对数。则代码为:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[1000001];
ll fact[1000001];
int main(){
	int n;
	ll ans=0;
	cin>>n;
	for(ll i=1;i<=n;i++){
		cin>>a[i];
	}
	fact[0]=1;
	for(ll i=1;i<=n;i++){
		fact[i]=fact[i-1]*i%998244353;
	}
	for(ll i=1;i<=n;i++){
		ll sum=0;
		for(ll j=i+1;j<=n;j++){
			if(a[i]>a[j]){
				sum++;
			}
		}
		ans+=(sum*fact[n-i])%998244353;
	}
	cout<<(ans+1)%998244353;
	return 0;
} 

结果仅仅只有50分...

但是,当我们看到逆序对的时候,我们会想到O(nlogn)算法中的用树状数组求逆序对数。

代码为:

#include<bits/stdc++.h>
using  namespace std;
#define ll long long
#define mol 998244353
ll a[1000005];
ll c[1000005];
long long ans[1000005];
int lowbits(ll x){
	return x&-x;
}
void floo(ll n){
	ans[0]=1;
	for(int i=1;i<=n;i++)ans[i]=(ans[i-1]*i)%mol;
}
ll downdate(ll n,ll x){
	ll sum=0;
	while(x>0){
		sum+=c[x];
		x-=lowbits(x);
	}
	return sum;
}
void update(ll n,ll x){
	while(x<=n){
		c[x]+=1;
		x+=lowbits(x);
	}
}
int main(){
	ll n,anser=0;
	cin>>n;
	floo(n);
	for(ll i=1;i<=n;i++){
		cin>>a[i];
	}
	for(ll i=n;i>=1;i--){
		ll sum=downdate(n,a[i]);
		anser+=(sum*ans[n-i])%mol;
		update(n,a[i]);
	}
	cout<<(anser+1)%mol;
	return 0;
}

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值