康托展开
题目描述
给出一个数N,再给出N的全排列的某一个排列,问该排列在全排列中的次序是多少?例如3的全排列中,123排第一位,321排最后一位
输入描述
第一行为一个数N,第二行为N的全排列的某一个排列
输出描述
一个整数,表示该排列在全排列中的次序
样例
输入
3
1 2 3
输出
1
思路
设有 n 个数,可以有组成不同(种)的排列组合,康托展开表示的就是是当前排列组合在 n 个
不同元素的全排列中的名次。
那我们首先确定肯定不能暴力
因为本题范围是20,而20个字符全排列是一个很恐怖的数字:
是多少呢?20!20!20! 即 2.432902e+182.432902e+182.432902e+18
翻译成是个人都能看懂的就是 2.432902×10182.432902\times10^{18}2.432902×1018
这别说是时间超限都够时间它轮回一趟了
那我们就该引入本题的正解了:
观察样例,由于本题是字典序排列,所以只要比排列数小的数字都该排在它的前面
单说可能不太直观,举个例子25431
先看万位2:
因为比25431小的排列数都该排在它的前面,所以以1开头的排列数就都排在了它的前面
以1开头的五位排列数有多少呢?
4!4!4! 因为第一位确定是1然后剩下四位全排列
所以25431光万位可以排除1×4!1\times4!1×4!个数,注意假如万位是xxx是相应的要把1改成x−1x-1x−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;
}