康托展开(八数码问题)

来源:互联网 发布:弯刀对着瓢切菜 知乎 编辑:程序博客网 时间:2024/05/17 08:49

定义:

把一个整数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;i<a[x];i++)        if (!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<cstdio>#include<cstring>#include<algorithm>#define MAXN 3#define MAXM 400000using 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;j<s[i];j++)            if (!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<w){//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;}