康拓展开
当我们去搜康托展开这个关键字的时候,映入眼帘的是下面的一大堆公式:
其中X为康拓展开值
为整数,且
且表示袁数在当前未出现的元素是排第几个。
很不错,这样一下子会把人弄得搞陀不清(湖南方言)。
所以,当我们学习一个算法的时候,首当其冲的应该是要知道此算法到底是做什么用的?
百度百科是这样解释的:康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
这是什么意思呢?
简单的来说就是将一个全排列的序列压缩成一个数。
例如,当我们定义全排列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。
我们手动模拟一下上面公式的具体操作
,因为此时元素3在当前未出现的元素中排第二个,未出现元素即变为【1 2 4 5】(注意,这里序列排序是从0开始的);
,因为此时元素1在当前未出现的元素中排第零个,未出现元素即变为【2 4 5】(同上)
,因为此时元素4在当前未出现的元素中排第一个,未出现元素即变为【2 5】(同上);
,因为此时元素2在当前未出现的元素中排第零个,未出现元素即变为【5】(同上);
,因为此时元素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
则:
所以,原序列为=3 1 4 2 5
现在,我们可以做一下题目了。
我们在计算康拓展开的值的时求的时候,其实可以认为以下标为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;
}