康托展开
题目描述
给出一个数N,再给出N的全排列的某一个排列,问该排列在全排列中的次序是多少?例如3的全排列中,123排第一位,321排最后一位
输入描述
第一行为一个数N,第二行为N的全排列的某一个排列
输出描述
一个整数,表示该排列在全排列中的次序
样例
输入
3
1 2 3
输出
1
思路
设有 n 个数,可以有组成不同(种)的排列组合,康托展开表示的就是是当前排列组合在 n 个
不同元素的全排列中的名次。
那我们首先确定肯定不能暴力
因为本题范围是20,而20个字符全排列是一个很恐怖的数字:
是多少呢?
20
!
20!
20! 即
2.432902
e
+
18
2.432902e+18
2.432902e+18
翻译成是个人都能看懂的就是
2.432902
×
1
0
18
2.432902\times10^{18}
2.432902×1018
这别说是时间超限都够时间它轮回一趟了
那我们就该引入本题的正解了:
观察样例,由于本题是字典序排列,所以只要比排列数小的数字都该排在它的前面
单说可能不太直观,举个例子25431
先看万位2:
因为比25431小的排列数都该排在它的前面,所以以1开头的排列数就都排在了它的前面
以1开头的五位排列数有多少呢?
4
!
4!
4! 因为第一位确定是1然后剩下四位全排列
所以25431光万位可以排除
1
×
4
!
1\times4!
1×4!个数,注意假如万位是
x
x
x是相应的要把1改成
x
−
1
x-1
x−1
同理,观察往后的数,可得25431前面有:
X
=
X=
X=
1
×
4
!
+
3
×
3
!
+
2
×
2
!
+
1
×
1
!
+
0
×
0
!
1\times4!+3\times3!+2\times2!+1\times1!+0\times0!
1×4!+3×3!+2×2!+1×1!+0×0!个数
所以我们可以总结成公式的形式:
X
=
X=
X=
a
[
1
]
×
(
n
−
1
)
!
+
a
[
2
]
×
(
n
−
2
)
!
+
a
[
3
]
×
(
n
−
3
)
!
+
.
.
.
+
a
[
n
−
1
]
×
0
!
a[1]\times(n-1)!+a[2]\times(n-2)!+a[3]\times(n-3)!+...+a[n-1]\times0!
a[1]×(n−1)!+a[2]×(n−2)!+a[3]×(n−3)!+...+a[n−1]×0!
之后的代码相信聪明的读者是可以自己写出来哒( ̄▽ ̄)~*
注意:由于康托展开求的是输入排列数前面有多少排列数,所以算出答案要+1
AC代码
#include<iostream>
#include<cstdio>
using namespace std;
int N;
int a[25]; //排列数组
long long p[25]; //阶乘数组
long long sum = 1; //次序
//康托展开函数(模版,建议寄到小本本上(`・ω・´))
long long cantor(int n) {
//阶乘
p[0] = 1;
for (int i = 1; i <= 15; i++) {
p[i] = p[i - 1] * (i + 1);
}
//重中之重
long long s = 0;
for (int i = 0; i < n - 1; i++) {
//枚举当前位前面有多少个数没有被用过
s = 0;
for (int j = i + 1; j < n; j++) {
if (a[i] > a[j]) {
s++;
}
}
sum += s * p[n - 2 - i]; //用可用数数量*阶乘
}
return sum;
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%d", &a[i]);
}
printf("%lld", cantor(N));
return 0;
}