康託展開
注意: 本文所有的排名均是從第0名開始。
康託展開:
已知一個$1—n$的排列$A=\{a_1,a_2,\cdots,a_n\}$,求它在所有排列中的字典序排名。
常用於將$n$個全排列對映到$n!$個自然數中。
求解這個問題的思路大概是下面這樣的:
$(1)$ $A$的排名=字典序小於$A$的排列個數。所以只需要知道有多少個排列比$A$小就好了w
$(2)$ 我們按位考慮,第一位小於$a_1$的所有排列肯定比$A$小,這部分有$(a_{1}-1)\times (n-1)!$個。
$(3)$ 在第一個數等於$a_1$的所有排列中,第二位小於$a_2$的所有排列也肯定比$A$小。
那麼這部分有$(a_{2}-1)\times (n-2)!$個對不對?
但是這個時候出現了一個問題:
如果$a_{1}<a_{2}$,那麼第二位就不能再用$a_1$這個數了(因為是排列)。
所以應該有$(a_{2}-2)\times (n-2)!$個。
當然如果$a_{1}>a_{2}$就不需要額外$-1$了w
$(4)$ 現在我們把$(3)$的結論推廣,
前$i-1$位與$A$相同且第$i$位小於$A$的排列,共有$(a_{i}-cnt_{i}-1)\times (n-i)!$個。
其中$cnt_i$表示$\{a_{1},a_{2},\cdots ,a_{i-1}\}$中小於$a_i$的個數。
顯然所有這樣的排列加起來就是比$A$小的排列總數(有序統計)。
$(5)$ 注意到$a_{i}-cnt_{i}-1$還等於後$\{a_{i},a_{i+1},\cdots ,a_{n}\}$中小於$a_i$的個數(因為是排列……)。
所以我們就得到了康託展開公式:
$Rank_{A}=b_{n}\times (n-1)!+b_{n-1}\times (n-2)!+\cdots +b_1 \times 0!$
其中$b_{i}$表示$a_i$在當前未出現的$a$中排在第幾個。
關於實現,只需要按定義模擬即可。
程式碼:
inline int Cantor(){ int rank=0; for(int i=1;i<=N;i++){ int s=0; for(int j=i+1;j<=N;j++) s+=(A[j]<A[i]); rank+=s*jc[N-i]; } return rank; }
逆康託展開:
和上面相反,已知某排列的排名$x$,求這個排列。
解決思路基本沒區別(說是相反也行):
假設我們現在要求$a_i$的值,首先可以得到$b_i=x\div (n-i)!$。
那麼也就是知道了$a_i$在當前未出現過的$a$中的排名。
但僅僅知道這個不能直接計算,所以我們還要記錄一下前$i-1$位出現過的$a$。
然後$O(n)$列舉求出答案。
下面是一個例子:
此時$n=8,i=4$,前$3$位出現了$1,4,6$。
假設$b_i=3$,那麼$a_i$在未出現的數裡排名第$3$。
由於排名是從$0$開始的,$a_i$就是灰色的第$4$個數$7$。
程式碼:
inline void inv_Cantor(int x){ memset(vis,0,sizeof(vis)); for(int i=1;i<=N;i++){ int tp=x/jc[N-i]; for(int j=1;j<=N;j++){ if(vis[j]) continue; if(tp==0){ vis[j]=1,A[i]=j; break; } tp--; } x=x%jc[N-i]; } return; }
模板題目: loj10027
程式碼:( 這題的排名是從1開始的 )
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; #define MAXN 10 #define MAXM 1000005 #define INF 0x7fffffff #define ll long long inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } int N=8,dis[MAXM],com[MAXM]; int jc[MAXN],A[MAXN],last[MAXM]; bool vis[MAXM],vvis[MAXN]; inline int Cantor(){ int rank=0; for(int i=1;i<=N;i++){ int s=0; for(int j=i+1;j<=N;j++) s+=(A[j]<A[i]); rank+=s*jc[N-i]; } return rank+1; } inline void inv_Cantor(int x){ x-=1; memset(vvis,0,sizeof(vvis)); for(int i=1;i<=N;i++){ int tp=x/jc[N-i]; for(int j=1;j<=N;j++){ if(vvis[j]) continue; if(tp==0){ vvis[j]=1,A[i]=j; break; } tp--; } x=x%jc[N-i]; } return; } inline int get1(int x){ inv_Cantor(x); swap(A[1],A[8]); swap(A[2],A[7]); swap(A[3],A[6]); swap(A[4],A[5]); return Cantor(); } inline int get2(int x){ inv_Cantor(x); swap(A[1],A[4]); swap(A[2],A[4]); swap(A[3],A[4]); swap(A[5],A[8]); swap(A[5],A[6]); swap(A[6],A[7]); return Cantor(); } inline int get3(int x){ inv_Cantor(x); swap(A[3],A[7]); swap(A[2],A[3]); swap(A[6],A[7]); return Cantor(); } inline void init(){ jc[0]=1; for(int i=1;i<=N;i++) jc[i]=jc[i-1]*i; return; } inline void print(int u){ if(u==1) return; print(com[u]); if(last[u]==1) printf("A"); if(last[u]==2) printf("B"); if(last[u]==3) printf("C"); } void BFS(){ int end=Cantor(); queue<int> q; q.push(1); dis[1]=0,vis[1]=1; while(!q.empty()){ int u=q.front(); q.pop(); if(u==end){ printf("%d\n",dis[u]); print(u); printf("\n"); break; } int t1=get1(u),t2=get2(u),t3=get3(u); if(!vis[t1]){ dis[t1]=dis[u]+1,vis[t1]=1; last[t1]=1,com[t1]=u; q.push(t1); } if(!vis[t2]){ dis[t2]=dis[u]+1,vis[t2]=1; last[t2]=2,com[t2]=u; q.push(t2); } if(!vis[t3]){ dis[t3]=dis[u]+1,vis[t3]=1; last[t3]=3,com[t3]=u; q.push(t3); } } return; } int main(){ for(int i=1;i<=N;i++) A[i]=read(); init(); BFS(); return 0; }