康托展开(八数码问题)

定义:

把一个整数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;
}

你可能感兴趣的:(算法/总结/游记,组合---康托展开,蒟蒻zxl的Blog专栏)