把一个整数X展开成如下形式:
X=a[n] ∗ (n-1)!+a[n-1] ∗ (n-2)!+…+a[i]*(i-1)!+…+a[2]*1!+a[1]*0![1]
其中a[i]为当前未出现的元素中是排在第几个(从0开始),并且0<=a[i] < i(1<=i<=n)
求一个数字串排列在字典序下的编号(即第几个)。当然也可以倒着用,即给你编号求数字串。
可以对一些算法进行优化,有点hash的味道。
①求一个数字串排列在字典序下的编号:
其实定义里头的那个公式求的就是排列a之前的排列数。代码模拟一下即可。
这里采用的是递归实现:
long long dfs(int x){
if (x>n)
return 0;
long long sum=0;
int num=0;
for (int i=1;iif (!f[i])//如果这个数并没有出现过
num++;
f[a[x]]=true;
sum+=num*jc[n-x];//jc[i]即i的阶乘
sum+=dfs(x+1);//递归
return sum;
}
②倒着用:
反过来想,对于当前的编号n,我们要取的数。
不难发现,第n位的数即为num/jc[sum-n]。
仍然为递归实现:
void dfs1(long long x,int num){
if (num==n){//因为题目要求行末不能有空格
for (int i=1;i<=n;i++)//最后一个数字其实已经确定,即仍然没有出现的那个
if (!f[i]){
printf("%d",i);
break;
}
return;
}
int dd=x/jc[n-num];
int now=dd;
for (int i=1;i<=n;i++){
if (!f[i])
if (dd==0){
printf("%d ",i);
f[i]=true;
break;
}
else
dd--;
}
dfs1(x-now*jc[n-num],num+1);//递归
}
③算法优化:
这里举一个最经典的例子:八数码问题。
给出一个3*3的矩阵,其中有一个格子是空格,其他都是数字,每次移动可以将格子附近的数字移到格子中,同时原先的数字变成格子。给定一个初始状态和最终状态,求最少步数。
当然,这道题的解法很多,但这里只讲康托展开。
简单的BFS的话是会T掉的,因为判重的问题。而康托展开在这道题中的作用就是判重。把3*3的序列转化为其字典序,就能够存的下了(jc[9]=362880,即有不到40万的状态)。判重也就能实现了。
代码(写的太烂,跑得比较慢,二维压成一维就可以省去我的calc的过程):
#include
#include
#include
#define MAXN 3
#define MAXM 400000
using namespace std;
const int t1[4]={0,1,0,-1};
const int t2[4]={1,0,-1,0};
bool num[MAXM+5];
int que[MAXM+5][2];
int a[MAXN+5][MAXN+5],b[MAXN+5][MAXN+5],jc[MAXN*MAXN+5];
int n=3;
bool f[MAXM+5];
int calc(int s[MAXN+5][MAXN+5]){
int sum=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
sum=sum*10+s[i][j];
return sum;
}
void fz(int s[MAXN+5][MAXN+5],int zhi,int &x,int &y){
for (int i=n;i>=1;i--)
for (int j=n;j>=1;j--){
s[i][j]=zhi%10;
zhi/=10;
if (s[i][j]==0){
x=i; y=j;
}
}
}
int jisuan(int x){//康托展开
memset(f,false,sizeof(f));
int s[MAXN*MAXN+5];
for (int i=MAXN*MAXN;i>=1;i--){
s[i]=x%10;
x/=10;
}
int sum=0;
for (int i=1;i<=MAXN*MAXN;i++){
int num=0;
for (int j=0;jif (!f[j])
num++;
f[s[i]]=true;
sum+=num*jc[MAXN*MAXN-i];
}
return sum;
}
int main(){
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]);//初始状态
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&b[i][j]);//目标状态
jc[1]=1;
for (int i=2;i<=MAXN*MAXN;i++)
jc[i]=jc[i-1]*i;//算阶乘
int r=0,w=1;
que[1][1]=calc(a);
que[1][0]=1;
memset(num,0,sizeof(num));
while (r//BFS
int xx[MAXN+5][MAXN+5];
int x,y;
memset(xx,0,sizeof(xx));
if (que[++r][1]==calc(b)){
printf("%d\n",que[r][0]);
break;
}
fz(xx,que[r][1],x,y);
for (int i=0;i<=n;i++){
int p=x+t1[i],q=y+t2[i];
if (p>=1&&p<=3&&q>=1&&q<=3){
swap(xx[x][y],xx[p][q]);
int l=calc(xx);
int k=jisuan(l);
if (!num[k]){
que[++w][1]=calc(xx);
que[w][0]=que[r][0]+1;
num[k]=true;
}
swap(xx[x][y],xx[p][q]);
}
}
}
return 0;
}