HDU - 1043 Eight 【打表、康托展开、反向bfs】
题目简述
分析
- 康托展开
对于一个有n!种排列方式的排列组合,康托展开表示的就是是当前排列组合在全排列中的位次。
我们常构建下式来表示康托展开值:
其中\(a_i\)表示比第i位数字小且前面未出现的数字个数。
举个例子:对于集合{1,2,3,4},我们取其中一种排列(2 1 4 3)来分析。
①对于第一位2,小于2的数字只有1,故\(a_1=1\);
②对于第二位1,没有小于1的数字,故\(a_2=0\);
③对于第三位4,小于4的数字只有3,因为1和2出现在了第二位和第一位,故\(a_3=1\);
④对于第四位3,没有小于3的数字,故\(a_4=0\)。
所以康托展开值\(\texttt{x=1*3!+0*2!+1*1!+0*0!=7}\),即比(2 1 4 3)小的排列有7个,故(2 1 4 3)排第8名。
代码如下:
int cantor(int a[],int n){
int x=0;
for(int i=0;i<n;i++){
int small=0;
for(int j=i+1;j<n;j++){
if(a[j]<a[i]) small++;
}
x+=small*fact[n-i-1];
}
return x+1;
} //康托展开
- 逆康托展开
康托展开实质是建立全排列与自然数的双射,故而是可逆的。
还是以(2 1 4 3)为例,假设我们已知由1234组成的某个全排列,其康托展开值为7,那我们可以进行逆运算:
①7/3!=1余1,说明有1个数比第一位小,故\(a_1=2\);
②1/2!=0余1,说明没有数比第二位小,故\(a_2=1\);
③1/1!=1余0,说明有1个数比第三位小,故\(a_3=4\)(算上已出现的1和2,再加一位);
④0/0!=0,说明没有数比第四位小,故\(a_4=3\)。
故逆康托展开的结果是(2 1 4 3)。
一般化上述过程,即可得到对应代码:
void recantor(int a[],int val,int n){
vector<int> v;
for(int i=0;i<n;i++) v.push_back(i);
for(int i=0;i<n;i++){
int l=0,r=0;
l=val/fact[n-i-1];
r=val%fact[n-i-1];
val=r;
sort(v.begin(),v.end());
a[i]=v[l];
v.erase(v.begin()+l);
}
return;
}//逆康托展开
- bfs过程
首先我们需要确定的是,因为已知终点,故逆序bfs是优于从起点开始的正序\(bfs\)的(后者实测\(MLE\))
在每次遍历到新位置\(temp\)时,我们将其与上一个状态\(curr\)的位置交换,将\(temp\)的康托展开值进行更新,同时记录轨迹(将康托展开值变为下标)。
注意G++会炸,转C++提交(手动流汗黄豆)。
AC代码参考:https://blog.csdn.net/laaahu/article/details/96467188

浙公网安备 33010602011771号