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的阶乘为1for(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的阶乘为1for(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;
}