HDOJ1027 Ignatius and the Princess II (康托展开及康托展开逆运算)

Ignatius and the Princess I         

Problem Description

Now our hero finds the door to the BEelzebub feng5166. He opens the door and finds feng5166 is about to kill our pretty Princess. But now the BEelzebub has to beat our hero first. feng5166 says, "I have three question for you, if you can work them out, I will release the Princess, or you will be my dinner, too." Ignatius says confidently, "OK, at last, I will save the Princess."

"Now I will show you the first problem." feng5166 says, "Given a sequence of number 1 to N, we define that 1,2,3...N-1,N is the smallest sequence among all the sequence which can be composed with number 1 to N(each number can be and should be use only once in this problem). So it's easy to see the second smallest sequence is 1,2,3...N,N-1. Now I will give you two numbers, N and M. You should tell me the Mth smallest sequence which is composed with number 1 to N. It's easy, isn't is? Hahahahaha......"
Can you help Ignatius to solve this problem?

Input

The input contains several test cases. Each test case consists of two numbers, N and M(1<=N<=1000, 1<=M<=10000). You may assume that there is always a sequence satisfied the BEelzebub's demand. The input is terminated by the end of file.

Output

For each test case, you only have to output the sequence satisfied the BEelzebub's demand. When output a sequence, you should print a space between two numbers, but do not output any spaces after the last number.

Sample Input

6 4

11 8

 

 

Sample Output

1 2 3 5 6 4

1 2 3 4 5 6 7 9 8 11 10

(转自http://blog.csdn.net/lttree/article/details/26244269)

#include <iostream>
using namespace std;
int fac[]={1,1,2,6,24,120,720,5040,40320};
int ans[10001],len; // 存储答案
// 康托展开的逆   n为要对几位数排序,k为第几个数,num为这n个数应该从多少开始
void reverse_kangtuo(int n,int k,int num)
{
    int i, j, t, vst[11]={0};
    char s[11];

    --k;
    for (i=0; i<n; i++)
    {
        t = k/fac[n-i-1];
        for (j=1; j<=n; j++)
            if (!vst[j])
            {
                if (t == 0) break;
                --t;
            }
        s[i] = '0'+j;
        vst[j] = 1;
        k %= fac[n-i-1];
    }
    // 排序后的赋给答案数组
    for(int kk=0;kk<n;++kk)
        ans[len++]=s[kk]-'1'+num;
}

int main()
{
    int n,m;
    int i,j,temp1,temp2;
    while( cin>>n>>m )
    {
        i=1;
        len=0;
        if( n>8 )
        {
            temp1=n%8;
            temp2=(n/8-1)*8;
            // 相应答案赋值
            for(;i<=temp1;++i)
                ans[len++]=i;
            for(j=0;j<temp2;++j,++i)
                ans[len++]=i;
            reverse_kangtuo(8,m,i);
        }
        else    reverse_kangtuo(n,m,i);

        // 输出,注意最后一个数后面没有空格
        for(i=0;i<len-1;++i)
            cout<<ans[i]<<" ";
        cout<<ans[len-1]<<endl;
    }
    return 0;
}



康托展开及康托展开逆运算详解




  1. 康托展开是什么?



判断这个数在其各个数字全排列中从小到大排第几位。


比如 132,在1、2、3的全排列中排第2位。




2.康托展开有啥用呢?




维基:n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出对应的全排列。


它可以应用于哈希表中空间压缩,而且在搜索某些类型题时,将VIS数组量压缩。比如:八数码、魔板。




3.康托展开求法:




从头判断,至尾结束:


当前数后面比他小的数的个数*后面数的个数的阶乘;(技巧:先算出阶乘先)


再将所有乘积求和,最后加1。


比如2143 这个数,求其展开:


比该数小的数有1*3!+ 0*2!+ 1*1!=7个,所以该数排第8的位置。


用程序来实现就是:


int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的阶乘为fac[i] 


    // 康托展开-> 表示数字a是 a的全排列中从小到大排,排第几 


    // n表示1~n个数  a数组表示数字。 


    int kangtuo(int n,char a[]) 


    { 


        int i,j,t,sum; 


        sum=0; 


        for( i=0; i<n ;++i) 


        { 


            t=0; 


            for(j=i+1;j<n;++j) 


                if( a[i]>a[j] ) 


                    ++t; 


            sum+=t*fac[n-i-1]; 


        } 


        return sum+1; 


    } 








4.康托展开的逆:




康托展开是一个全排列到自然数的双射,可以作为哈希函数。


所以当然也可以求逆运算了。


逆运算的方法:


假设求4位数中第19个位置的数字。


① 19减去1  → 18


② 18 对3!作除法 → 得3余0


③  0对2!作除法 → 得0余0


④  0对1!作除法 → 得0余0


据上面的可知:


我们第一位数(最左面的数),比第一位数小的数有3个,显然 第一位数为→ 4


比第二位数小的数字有0个,所以 第二位数为→1


比第三位数小的数字有0个,因为1已经用过,所以第三位数为→2


第四位数剩下 3


该数字为  4123  (正解)




用代码实现上述步骤为:


    int  fac[] = {1,1,2,6,24,120,720,5040,40320}; 


    //康托展开的逆运算,{1...n}的全排列,中的第k个数为s[] 


char s[];


void reverse_kangtuo(int n,int k) 


    { 


        int i, j, t, vst[8]={0}; 


        --k;                    //首先-1得到排在所求数前面数的个数


        for (i=0; i<n; i++)     //依次确定第一到最后一位数,


        {                  


            t = k/fac[n-i-1];     //t=剩余的前面数的个数/需要确定的当前数位后面位数的阶乘。

                                  //余数作被除数,商为当前位对应数判断依据。

            for (j=1; j<=n; j++) //从1开始数到第t+1个没有用过的数即为答案。


                if (!vst[j]) 


                { 


                    if (t == 0) break;


                    --t; 


                } 


            s[i] = j;      //想想为啥是j;


            vst[j] = 1; 


            k %= fac[n-i-1];  //拿余数循环下去。


        } 


    } 


#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int a[1005];
int vis[1005];
int fac[]={1,1,2,6,24,120,720,5040,40320};

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
        {
            int i,j,t;
            memset(vis,0,sizeof(vis));
            m--;
            for(i=1;i<=n;i++)
                {
                    t=m/fac[n-i];
                    for(j=1;j<=n;j++)
                        {
                            if(!vis[j])
                                {
                                    if(t==0)
                                        break;
                                    t--;    
                                }     
                        }
                    a[i]=j;
                    vis[j]=1;
                    m=m%fac[n-i];
                }
            cout<<a[1];
            for(int i=2;i<=n;i++) 
                {
                    cout<<' '<<a[i];
                }
            cout<<endl;         
        }
    return 0;
} 
原文地址:https://www.cnblogs.com/biggan/p/7445696.html