1.定义
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆(划重点,要考的)的。
当然了通俗易懂的来说的话,康托展开就相当于给每个全排列加个序号,每个全排列都有其自己独特的编号,比如说123456的编号为1,123465的编号为2(这时候是不是感觉有一点儿深搜的感觉,就相当于其全排列编号就是其是第几个全排列式子,反正我是这么理解的,个位酌情理解即可)
2.康托展开式
这里我们来讲解一下这里的ai指的都是啥,我们的ai指的是对于全排列中的从右往左的第i个数,有多少个本来该出现在前面的数没有出现,比如说54231这个排列方式,此时的a5应为4,因为5的前面本应该有4个数,但是一个也没有了,因此a5=4,那么5就可以固定了,再看a2,,前面三个数都已经固定了,看后续的,3的前面只有一个1,没有出现过,所以a2=1;,因此我们就可以通过上述公式来计算其康拓表达式的值,但是我们算出来了,还需加上一个1,因为每个全排列的序号最低为1,而也就是按顺序的全排列表,其康托展开式的值为0,12345,其康托展开式的值为0
3.康托展开式代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
//先把阶乘算出来
int f[20];
void jiecheng(int n)
{
f[0] = f[1] = 1; // 0的阶乘为1
for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i;
}
//康托展开
int kangtuo()
{
int ans = 1; //注意,因为 12345 是算作0开始计算的,最后结果要把12345看作是第一个
int len = str.length();
for(int i = 0; i < len; i++)
{
int tmp = 0;//用来计数的
for(int j = i + 1; j < len; j++)
{
if(str[i] > str[j]) tmp++;
//计算str[i]是第几大的数,或者说计算有几个比他小的数
}
ans += tmp * f[len - i - 1];
}
return ans;
}
int main()
{
jiecheng(10);
string str = "52413";
cout<<kangtuo()<<endl;
}
4.康托展开式的逆运算
ps:这里由于例子太难写了,所以直接借鉴ltrbless-CSDN博客神牛博客里面的一些例子了
在一开始,我么就已经说过了,康托展开式是全排列和自然数的双射,那么自然是可逆的
这里直接开栗子:
如果初始序列是12345(第一个),让你求第107个序列是什么。(按字典序递增)
这样计算:
先把107减1,因为康托展开里的初始序列编号为0
然后计算下后缀积:
1 2 3 4 5
5! 4! 3! 2!1! 0!
120 24 6 2 1 1
106 / 4! = 4 ······ 10 有4个比它小的所以因该是5 从(1,2,3,4,5)里选
10 / 3! = 1 ······ 4 有1个比它小的所以因该是2 从(1, 2, 3, 4)里选
4 / 2! = 2 ······ 0 有2个比它小的所以因该是4 从(1, 3, 4)里选
0 / 1! = 0 ······ 0 有0个比它小的所以因该是1 从(1,3)里选
0 / 0! = 0 ······ 0 有0个比它小的所以因该是3 从(3)里选
所以编号107的是 52413
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
//算出阶乘
int f[20];
int x, num;
void jie_cheng(int n)
{
f[0] = f[1] = 1; // 0的阶乘为1
for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i;
}
//康托逆展开
vector<char> vec; //存需要排列的字符
void rev_kangtuo(int k) //输出序号为 k 的字符序列
{
int n = vec.size(), len = 0;
string ans = "";
k--; // 算的时候是按 12345 是第0位
for(int i = 1; i <= n; i++){
int t = k / f[n - i]; // 第 i 位需要 第 t + 1 大的数
k %= f[n - i]; //剩下的几位需要提供的排列数
ans += vec[t] ; // vec[t] 就是第 t + 1 大的数
vec.erase(vec.begin() + t);
//用过就删了,不用vector用暴力也可以,就是说枚举,然后一个一个的比较大小,并记录有几个没用过的字符且字典序比它小
}
cout << ans << '\n';
}
// 假设展开后不超过10位
int main()
{
jie_cheng(10); // 预处里好阶乘
scanf("%d", &x); // 输入需要逆展开的数字
/************康托逆展开***********/
for(int i = 1; i <= 10; i++)
{
if(x / f[i] == 0) // 求出 x 逆展开所需的最小的位数,方便下面的初始化
{
num = i;
break;
}
}
for(int i = 1; i <= num; i++) vec.push_back(i + '0'); //输入的位数只要不小于num就可以
rev_kangtuo(x);
}
5.康托展开的例题
今天就先列举一个,后续会将另一个例题补上
题解:这题看题就是需要用到bfs这应该是我们脑子里面首先想到的,其次呢,这题有个不一样的地方就是需要用到康托展开,去吧每一种全排列的编号记录下来,然后用ans数组记录其 相应的步骤,然后最后输出就好了(这里要注意的的是,你输入的12345678,但其实是12348765),因此我们需要在后面给他转换一下
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include<string>
#include <queue>
using namespace std;
string start,end1;//初始的目标状态,和改变后的目标状态
string step[1000000];//用于记录整个流程的操作
int vis[1000000];//判断是否已经到达这个状态
int num[10]={1},pos[10];//num数组用于计算阶乘,pos数组用于装start中的每个数字在其start中的位置
struct node
{
string step;//用于记录执行的操作
string str;//目标字符串
int hash;//对应的康托展开值
};
int ni(string &s)//求逆序数
{
int sum=0;
for(int i=0;i<7;i++)
{
int cnt=0;
for(int j=i+1;j<8;j++)
{
if(s[i]>s[j])
cnt++;
}
sum+=cnt*num[7-i];
}
return sum;
}
//执行三种操作
void doa(string &s)
{
for(int i=0;i<4;i++)
{
swap(s[i],s[i+4]);
}
}
void dob(string &s)
{
char tmp=s[3];
for(int i=2;i>=0;i--)
{
s[i+1]=s[i];
}
s[0]=tmp;
tmp=s[7];
for(int i=6;i>=4;i--)
{
s[i+1]=s[i];
}
s[4]=tmp;
}
void doc(string &s)
{
char tmp=s[1];
s[1]=s[5];
s[5]=s[6];
s[6]=s[2];
s[2]=tmp;
}
//广搜执行三种操作,找到每一种状态的操作放方式
void bfs()
{
memset(vis,0,sizeof(vis));
node now,next;
queue<node>q;
now.step = "";
now.str = start;
now.hash = ni(start);
q.push(now);
vis[ni(start)]=1;
step[ni(start)]="";
while(!q.empty())
{
now=q.front();
q.pop();
string t;
int cnt;
t=now.str;
doa(t);
cnt=ni(t);
while(vis[cnt]==0)
{
vis[cnt]=1;
next=now;
next.step+='A';
step[cnt]=next.step;
next.str=t;
next.hash=cnt;
q.push(next);
}
t=now.str;
dob(t);
cnt=ni(t);
while(vis[cnt]==0)
{
vis[cnt]=1;
next=now;
next.step+='B';
step[cnt]=next.step;
next.str=t;
next.hash=cnt;
q.push(next);
}
t=now.str;
doc(t);
cnt=ni(t);
while(vis[cnt]==0)
{
vis[cnt]=1;
next=now;
next.step+='C';
step[cnt]=next.step;
next.str=t;
next.hash=cnt;
q.push(next);
}
}
}
int main()
{
for(int i=1;i<10;i++)
{
num[i]=num[i-1]*i;
}
start="12345678";
bfs();
while(cin>>start>>end1)
{
swap(start[4], start[7]);
swap(start[6], start[5]);
swap(end1[4], end1[7]);
swap(end1[6], end1[5]);
for (int i = 0; i < 8; i++)
{
pos[start[i]-'0'] = i + 1;
}
for (int i = 0; i < 8; i++)
{
end1[i] = pos[end1[i]-'0'];//存储每个数在start数组的位置
}
int cnt;
cnt=ni(end1);
cout << step[cnt] << endl;
}
return 0;
}