一.康托展开
用处:首先康托展开是求1~n中任意排列得排名
(把 1~n 的所有排列按字典序排序,这个排列的位次就是它的排名)
康托展开可以在O() 的复杂度内求出一个排列的排名,在用到树状数组优化时可以做到
O( n log(n) )。
因为排列是按字典序排名的,因此越靠前的数字优先级越高。也就是说如果两个排列的某一位之前的数字都相同,那么如果这一位如果不相同,就按这一位排序。
X = a[n] * (n-1)! + a[n-1] * (n-2)! + …… + a[i] * (i-1)! + …… + a[2] * 1! + a[1] * 0!
式中,a[i]表示原数第i位之前有几个比他小得数没有出现,并且有0 <= a[i] < i(1 <= i <= n)
注意我们统计的是排名,因此最前面要 +1。;
所以最终的排列为X+1
康托展开()
int a[N];
int n, m;
int jc[N];
void init() {
jc[1] = 1;
for (int i = 2; i <= 1e6; i++) {
jc[i] = jc[i - 1] * i % mod;
}
}
void contor() {
int sum = 1;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++) {
int cnt = 0;//计数
for (int j = i + 1; j <= n; j++) {
if (a[i] > a[j]) cnt++;//后面比a[i]小的有多少
}
sum = (sum + (cnt * jc[n - i])) ;
}
cout << sum << endl;
}
树状数组优化的康托展开(nlogn)
#include<cstdio>
#include<iostream>
using namespace std;
#define int long long
#define lowbit(x) x&-x
#define endl "\n"
const int N = 1e6 + 7;
const int mod = 998244353;
int a[N];
int n, m;
int jc[N];
int tr[N];
void init() {
jc[1] = 1;
for (int i = 2; i <= 1e6; i++) {
jc[i] = jc[i - 1] * i % mod;
}
}
void add(int x) {
for (int i = x; i <= n; i += lowbit(i)) {
tr[i] += 1;
}
}
int query(int x) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += tr[i];
}
return res;
}
void contor() {
int sum = 1;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
sum = (sum + ((a[i] - 1) - query(a[i] - 1)) * jc[n - i] % mod) % mod;
add(a[i]);
}
//cout << query(4);
cout << sum << endl;
}
signed main() {
init();
scanf("%d", &n);
contor();
return 0;
}
二.逆康托展开
因为排列的排名和排列是一一对应的,所以康托展开满足双射关系,是可逆的。可以通过类似上面的过程倒推回来。X需要减1
逆康托的求解排列流程:
求解第n位的数字:X/(n-1)!= a 余数是 c ,也就是找a数列中的a+1小的数
求解第i位的数字: c/(i-1)!= b 余数是c,,也就是找a数列中的b+1小的数,把已找到的数排除
void reverse_contor() {
int k;
cin >> k;//排列第几
k--;
vector<int> rest; // 存放当前可选数,有序
for (int i = 1; i <= n; i++)
rest.push_back(i);
for (int i = n; i >= 1; i--) {
int r = k % jc[i - 1];
int t = k / jc[i - 1];
k = r;
cout << rest[t] << " ";//每次找到就输出
rest.erase(rest.begin() + t); // 移除选做当前位的数
}
cout << endl;
}
最后附上多次查询的代码
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
#define int long long
#define lowbit(x) x&-x
#define endl "\n"
const int N = 1e5 + 10;
const int mod = 998244353;
int n, m;
int jc[N];
//计算阶乘
void init() {
jc[1] = 1;
jc[0] = 1;
for (int i = 2; i <= 20; i++) {
jc[i] = jc[i - 1] * i ;
}
}
int contor(int x[]) {
int sum = 1;
for (int i = 1; i <= n; i++) {
int cnt = 0;//计数
for (int j = i + 1; j <= n; j++) {
if (x[i] > x[j]) cnt++;//后面比a[i]小的有多少
}
sum += (cnt * jc[n - i]);
}
// cout << sum << endl;
return sum ;
}
int val[25];
void reverse_contor() {
int k;
cin >> k;
k--;
vector<int> rest; // 存放当前可选数,有序
for (int i = 1; i <= n; i++)
rest.push_back(i);
for (int i = n; i >= 1; i--) {
int r = k % jc[i - 1];
int t = k / jc[i - 1];
k = r;
cout << rest[t] << " ";
rest.erase(rest.begin() + t); // 移除选做当前位的数
}
cout << endl;
}
signed main() {
init();
scanf("%lld%lld", &n, &m);
while (m--) {
string s;
cin >> s;
if (s[0] == 'P') {
reverse_contor();
} else if (s[0] == 'Q') {
for (int i = 1; i <= n; i++) scanf("%lld", &val[i]);
printf("%lld\n", contor(val));
}
}
return 0;
}