求一个排列在全排列中的排名。
好像是一个比较经典的问题,类似的问题还有,求子串的排名等等 我在胡说什么
康托展开说白了就是一个hash方法。
方法如下:
ans = 1+sigma(a[i]*(n-i))
例如:对于一个排列 1 2 3 4 5
其进制变换后就是 (0 0 0 0 0)
对于另一个排列 1 4 5 2 3
其进制变换后就是(0 2 2 0 0)
为啥呢?
构造方法如下:
首位是1,在(1,2,3,4,5)中是第一个,故为0
第二位4,在(4,5,2,3)中是第三个,故为2
第三位是5,在(5,2,3)中是第三个,故位2
第四位是2,在(2,3)中是第一个,故为0
第五个是3,自然为0
于是第i位的值就是a[i]减去它左边比他小的个数再减1
将其转换成10进制就是
for(int i=1;i<n;i++) ans = (ans+a[i])*(n-i)
显然,康托展开可以用树状数组搞
于是,代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+7;
const int mod = 998244353;
int n;
int sum[maxn],a[maxn];
void add(int p,int x)
{
for(int i=p;i<maxn;i+=(i & -i)) sum[i] += x;
}
int query(int p)
{
int ans = 0;
for(int i=p;i;i-=(i & -i)) ans += sum[i];
return ans;
}
int main()
{
scanf("%d",&n);
int ans = 0,fac = 1;
for(int i=n;i>=1;i--) scanf("%d",a+i);
for(int i=1;i<=n;i++)
{
int tmp = query(a[i]);
ans = (ans+1LL*tmp*fac)%mod;
fac = 1LL*fac*i%mod;
add(a[i],1);
}
printf("%d\n",ans+1);
return 0;
}
逆康托展开就是将数字映射成排列。
将上述变换逆变换即是逆康托展开。
例:
对于1,2,3,4,5.求排名为10的排列。
(1) 10-1 = 9
(2) 第一个数:9/(5-1)! = 0…9,所以第一个数就是第0个未出现的数,即:1
(3)第二个数: 9/(4-1)! = 1…3,所以第二个数就是第1个未出现的数,即:3
(4)第三个数: 3/(3-1)! = 1…1,所以第三个数就是第1个未出现的数,即:4
(5)第四个数: 1/(2-1)! = 1…0,所以第四个数就是第1个未出现的数,即:5
(6)第五个数: 0/(1-1)! = 0…0,所以第五个数就是第0个未出现的数,即:2
故第10个排列就是1 3 4 5 2。
代码先不放了,这大家肯定都会写啊…