康托展开也是很久以前就有所了解的算法了,今天才真正认真的码了两遍,还是写篇博客强化一下8
o( ̄┰ ̄*)ゞ 题目会补的,一定会的(悲)
定义
(逆)康托展开构建了一个在 正整数排列 和 正整数 之间的双射关系,即用一个正整数表示出该正整数序列按照字典序在所有排列中的排名(康托展开),根据排名反推出该排列(逆康托展开)。
康托展开
举一个实例来说明:
排列P:2 3 5 4 1
排名:36
我们来尝试构造出所有字典序小于P的排列。
首先,在第 1 位如果放置 1 ,得到的 1*4! 个排列按照字典序一定小于P。
否则,在第 1 位放置 2 (如果放置 3/4/5 显然不成立),考虑在第二位放置 1( 2 已经在第一位,而 3/4/5 显然不成立),所得到的 1*3! 个排列一定小于P。
否则,在第 2 位放置 3 ,考虑在第 3 位放置 1/4 ,所得到的 2*2!个排列一定小于P。
否则,在第 3 位放置 5 ,考虑在第 4 位放置 1 ,所得到的 1*1!个排列一定小于P。
否则,得到的是原排列,对小于P的排列无贡献。
得到小于P的排列个数 4! + 3! + 2*2! + 1! = 35
则P的排名为 35 + 1 = 36
关于代码实现,该位置的可选择的数的个数取决于 该位置之后 小于 原排列中该数的数的个数(前面的不动,也就是用掉了)
也就是说我们从当前位置的下一个开始遍历,遇到比当前位置更小的就x++,最后加上x*(n-i)!
fac[]为预处理的阶乘数组
上代码:
int cantor(int permutation[], int len)
{
int rk = 0;
for (int i = 0; i < len; i++)
{
int x = 0;
for (int j = i + 1; j < len; j++)
if (permutation[i] > permutation[j])
x++;
rk += x * fac[len - i - 1];
}
return rk + 1;
}
逆康托展开
同康托展开的想法,每次从rank中去掉尽量多的 (n-i)! ,若rank中有 x 个 (n-i)! ,该位选择剩下可选数中第 x+1 大的数
关于代码实现,直接进行模拟,vector<int> vec 维护剩下的数(这种问题一般规模很小,复杂度什么的都是浮云 :P)
上代码:
vector<int> incantor(int rk, int len)
{
rk--;
int x;
vector<int> vec, ans;
for (int i = 1; i <= len; i++)
vec.push_back(i);
for (int i = 1; i <= len; i++)
{
ans.push_back(vec[x = rk / fac[len - i]]);
vec.erase(vec.begin() + x);
rk %= fac[len - i];
}
return ans;
}
小结
下面是总代码:
#include <bits/stdc++.h>
using namespace std;
int fac[20]{1, 1}, num[20];
int cantor(int permutation[], int len)
{
int rk = 0;
for (int i = 0; i < len; i++)
{
int x = 0;
for (int j = i + 1; j < len; j++)
if (permutation[i] > permutation[j])
x++;
rk += x * fac[len - i - 1];
}
return rk + 1;
}
vector<int> incantor(int rk, int len)
{
rk--;
int x;
vector<int> vec, ans;
for (int i = 1; i <= len; i++)
vec.push_back(i);
for (int i = 1; i <= len; i++)
{
ans.push_back(vec[x = rk / fac[len - i]]);
vec.erase(vec.begin() + x);
rk %= fac[len - i];
}
return ans;
}
int main()
{
freopen("in.txt", "r", stdin);
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> num[i];
for (int i = 2; i <= n; i++)
fac[i] = fac[i - 1] * i;
int rk = cantor(num, n);
cout << "rank:" << rk << endl;
if (rk != 1)
{
vector<int> v = incantor(rk, n);
cout << "permutation:";
for (int i = 0; i < n; i++)
cout << v[i] << (i == n - 1 ? "" : " ");
cout << endl;
}
return 0;
}