康托展开&康托逆展开

康托展开:传送门

康托展开

康托展开的实质是计算当前序列在所有从小到大的全排列中的顺序,将全排列与自然数产生一个映射关系。而全排列的实质就是排列组合问题,那如何求该排列的康拓展开值呢?就是求出该比该排列字典序列小的序列个数加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;
}

太菜了,写的代码太垃圾了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赟家小菜鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值