1. 程式人生 > >康托展開與康托展開的逆運算

康托展開與康托展開的逆運算

給定 ret void style 逆運算 == 依次 code 一位數

康托展開用來求數組是該全排列的第幾項,康托展開的逆運用用於求全排列的第幾個排列。
已知對於1-n個數的全排列,總共的可能是n!種。對於一個已知的數列比如45321,在第一項是4時,表示第一項在此之前已經填放過1 2 3了,而後面的第二項至第五項則又是一個全排列,那麽此時的排列數就是3 * 4 !;第二位是5,則在放入5之前第二項已經放過1 2 3了,那麽排列數再加上3 * 3!;依次類推,最終答案為:ni=1=a[i]?(n?i)! 其中a[i]表示第i項前比它小的沒出現過的數字的個數。

易證,a[1]?(n?1)!>ni=2=a[i]?(n?i)! ,因此給定一個r表示是全排列的第r項,由r/(n?1)!

得到a[1],則第一位數即a[1] + 1;在r中減去(a[1] + 1) * (n -1)!,同理可以由$r / (n-2)!&得到a[2];依次類推,最後得到整個排列。

要註意的是 0! = 1

代碼模版:

ll fac[20]; //階乘

void getFac()
{
    fac[0] = 1;
    for(int i=1;i<20;i++)
        fac[i] = 1LL * fac[i-1] * i;
}

ll cantor(int *a,int len) //康托展開求a是全排列第幾項,a從1開始
{
    ll ret = 0;
    int vis[20
] = {0}; for(int i=1;i<=len-1;i++) { ll tp = 0; for(int j=1;j<a[i];j++) if(!vis[j]) tp ++; ret += 1LL * tp * fac[len - i]; vis[a[i]] = 1; } return ret + 1; } void _cantor(ll r,int len) //康托展開逆運算求第r個排列 { r --; int
vis[20] = {0},a[20]; for(int i=1;i<=len;i++) { ll tp = r / fac[len - i]; r -= tp * fac[len - i]; int j; for(j=1;j<=len;j++) if(!vis[j]) { if(tp == 0) break; tp --; } vis[j] = 1; a[i] = j; } for(int i=1;i<len;i++) printf("%d ",a[i]); printf("%d\n",a[len]); }

康托展開與康托展開的逆運算