康托展开:传送门
康托展开
康托展开的实质是计算当前序列在所有从小到大的全排列中的顺序,将全排列与自然数产生一个映射关系。而全排列的实质就是排列组合问题,那如何求该排列的康拓展开值呢?就是求出该比该排列字典序列小的序列个数加1,那如何求它之前的兄弟的个数呢?上公式:
其中ai代表原数的第i位在当前未出现的元素中占第几位(从0开始)
n代表n个元素
例如序列:1,2,3,4,5
其中:12345的康托展开的值为:
0 * 4!+0 * 3!+0 * 2!+0 * 1!+0 * 0!=0
0+1=1
43152的康托展开的值为:
3 * 4!+2 * 3!+0 * 2!+1 * 1!+0 * 0!=85
85+1=86
34152的康拓展开的值为:
3:a3=2(1,2,3,4,5,其中3的位置是2)
4:a4=2(1,2,4,5,其中4的位置是2)
1:a1=0(1,2,5,其中1的位置是0)
5:a5=1(2,5,其中5的位置是1)
2:a2=0(2,现在只有一个2,位置是0)
2 * 4!+2 * 3!+0 * 2!+1 * 1!+0 * 0!=61
61+1=62
上代码:
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define ll long long
int a[100], n,vis[100];
ll jiecheng(int x)
{
ll factorial = 1;
for (int i = 1; i <=x; i++)
factorial *= i;
return factorial;
}
ll cantor()
{
memset(vis, 0, sizeof vis);//初始化为0,用来标记这个数是否已经使用过
ll sum=1;//记录康拓值
ll ax;//记录ai的值
for (int i = 0; i < n; i++)
{
ax = 0;
for (int j = 0; j < n; j++)
{
if (a[i] > a[j] && !vis[a[j]])
{
ax++;
}
}
vis[a[i]] = 1;
sum += ax * jiecheng(n-1-i);
}
return sum;
}
int main()
{
while (std::cin >> n)
{
for (int i = 0; i < n; i++)
std::cin >> a[i];
std::cout << cantor() << std::endl;
}
return 0;
}
康拓逆展开
我们知道康拓展开是一个全排列与自然数的映射关系,并且是双射,我们可以根据全排列得到康拓展开值,那么如果给了一个康托值,我们是否可以的到这个全排列呢?那当然可以,因为他们的关系是双射,就像你与你的身份证号,你只有唯一的一个身份证号,一个身份证号也只能属于一个人,而康拓展开与全排列就是这样的关系。
那么我们知道了一个康托值如何求全排列呢?来例子:62——34152
既然他们的关系是双射,那么我们就可以把康托逆展开,就像y=f(x),现在我们要的是x=f(y);
首先:62-1=61;
那么X=61,再让我们回味一下X的公式:
现在我们知道了X的值,那么我们该如何得到an,an-1,an-2,…a1
其实很简单,我们只需要进行简单的除和取余操作,例如an=X/(n-1)!,
有人就会想其他的呢?an-1=X%(n-1)!/(n-2)! 是不是很简单呢?
现在我们得到了an这一系列的值了,如何得到字符呢,那我们就要考虑an的含义了,他代表什么呢?他代表了他在当前有几个弟弟。
例如 a5=61/4!=61/24=2,那说明现在这个数的前面有两个弟弟,那么当前状态他就是3.
好了现在我们明白如何求了,那就上代码:
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define ll long long
int a[100], n,ans,vis[100];
int jiecheng(int x)
{
ll factorial = 1;
for (int i = 1; i <=x; i++)
factorial *= i;
return factorial;
}
int find(int k)//寻找当前有K个兄弟,并且兄弟还没有用过
{
int sum=0;
if (k == 0)//k为0的时候比较特殊,单独拿出来
{
for (int i = 1; i <= n; i++)//k=0,说明他现在是还没有用过的第一个数
if (vis[i] == 0)
return i;
}
for (int i = 1; i <= n; i++)//开始找符合条件的数
{
if (vis[i] == 0)
sum++;
if (sum == k)
{
for (int j = i + 1; j <= n; j++)
if (vis[j] == 0)
return j;
}
}
return -1;
}
void cantor()
{
int k;
ans -= 1;
memset(vis, 0, sizeof vis);//标记这个数是否已经使用过
for (int i = 1; i <= n; i++)
{
k= ans / jiecheng(n - i);
a[i] = find(k);
vis[a[i]] = 1;
ans %= jiecheng(n - i);
}
for (int i = 1; i <= n; i++)
std::cout << a[i] << std::endl;
}
int main()
{
while (std::cin >> n)
{
std::cin >> ans;
cantor();
}
return 0;
}
太菜了,写的代码太垃圾了。