康托展开:给定一个全排列,计算其字典序。
公式:
r
a
n
k
[
x
]
=
a
1
∗
(
n
−
1
)
!
+
a
2
∗
(
n
−
2
)
!
…
a
i
∗
(
n
−
i
)
!
(
i
<
=
n
)
rank[x] = a_1*(n-1)!+a_2*(n-2)!\dots a_i*(n-i)! \ (i<=n)
rank[x]=a1∗(n−1)!+a2∗(n−2)!…ai∗(n−i)! (i<=n)
r
a
n
k
[
x
]
表
示
在
全
排
列
中
字
典
序
比
给
定
的
排
列
小
的
排
列
数
量
rank[x]表示在全排列中字典序比给定的排列小的排列数量
rank[x]表示在全排列中字典序比给定的排列小的排列数量
a
i
a_i
ai表示在未出现的元素中比当前位置的数小的数量,用树状数组来求
洛谷5367
求1∼N的一个给定全排列在所有1∼N全排列中的排名。
//复杂度nlog(n)
#include <cstdio>
#define mod 998244353
typedef long long ll;
int c[1000005];
ll product[1000005]; //计算阶乘
int n;
int lowbit(int x)
{
return x & -x;
}
void update(int x,int k)
{
for(; x <= n; x += lowbit(x))
{
c[x] += k;
}
}
int query(int x)
{
int sum = 0;
if( x == 0 ) return 0;
for (; x; x -= lowbit(x))
{
sum += c[x];
}
return sum;
}
int main()
{
ll ans = 0;
scanf("%d",&n);
product[0] = 1;
for (int i = 1; i <= n; i++)
{
product[i] = product[i-1] * i;
product[i] %= mod;
update(i,1); //初始化树状数组每个元素都为1
}
for (int i = 1; i <= n; i++)
{
int x;
scanf("%d",&x);
ans += query(x-1) * product[n-i]; //访问比当前元素小且未出现的个数
ans %= mod;
update(x,-1); //出现后更新为0
}
ans ++; //排名从1开始,所以加1
ans %= mod;
printf("%lld\n",ans);
return 0;
}
逆康托展开:给定n和该全排列的排名,求出这个全排列
康托展开的逆过程
/*
将排名减少一位
(1)除以(n-1)!,商即为a1,余数即后面数的和
(2)利用a1可计算出当前位置的元素
用余数继续重复上述运算,除以(n-i)!,算出ai
*/
#include <cstdio>
#include <vector>
using namespace std;
vector<int> res;
int vis[20]; //vis[i] = 0表示i未出现过
int product[20]; //由于阶乘爆炸式增长,所以n很小
void reverse_kangtuo(int n,int rank)
{
for (int i = 1; i <= n; i++)
{
int t = rank / product[n-i];
rank %= product[n-i];
for (int j = 1; j <= n; j++)
{
if( t == 0 && vis[j] == 0 )
{
res.push_back(j);
vis[j] = 1;
break;
}
if( !vis[j] ) t--;
}
}
}
int main()
{
int n,rank;
scanf("%d%d",&n,&rank);
product[0] = 1;
for (int i = 1; i <= n; i++)
{
product[i] = product[i-1] * i;
}
reverse_kangtuo(n,rank-1);
for (int i = 0; i < res.size(); i++)
{
printf("%d ",res[i]);
}
return 0;
}