康託展開和康託展開的逆運算
八數碼問題不用康託展開判斷重複8s,用康託展開判斷重複30MS。康託展開最大最明顯的作用就是在判斷狀態是否重複方面了,其實屬於hash的一個技巧。
一、康託展開
【問題背景】對於一個有n個不同元素的集合{1,2,3,4,...,n}的從小到大排序(從大到小 同理)的全排列 顯然它有n!項。如n=4,那麼就有4!=4×3×2×1=24項。
與自然數1,2,3,4,-----n!與之一一對應。比如 1~4四個數的全排列按字典序如下:
1234:第1個
1243:第2個
1324:第3個
1342:第4個
1423:第5個
1432:第6個
2134:第7個
2143:第8個
2314: 第9個
2413:第11個
2431:第12個
3124:第13個
3142:第14個
3214:第15個
3241:第16個
3412:第17個
3421:第18個
4123:第19個
4132:第20個
4213:第21個
4231:第22個
4312:第23個
4321:第24個
【主要問題】
例1:求4132是第幾個排列? 看上面就知道答案就是:20。 那麼是怎麼算的呢?
解:總共4個數,所以n=4.ans:=0;
第一個數是4,研究比4小的並且還沒有出現過的數有3個:1,2,3。那麼ans:=ans+ 3*(n-1)!
所以 ans:= ans+ 3* 3*2*1 =18
第二個數是1,研究比1小的並且還沒有出現過的數為 0個。那麼ans:=ans+ 0 * (n-2)!,那麼ans不變。
第四個數是2,研究比2小的並且還沒有出現過的數為0個。那麼ans不變。其實最後一個可以不研究了,比它大和比它小的全都出現過了。 最後ans怎麼等於19啊??代表它前面有19個排列嘛,那麼4132自己就是第20個羅( 最後ans:=ans+1)
例2:問45231是第幾個排列?
4 5 2 3 1
ans:= 3*4! + 3*3! + 1*2! + 1*1! + 0*0! + 1 =94
#include<cstdio> #include<bits/stdc++.h> using namespace std; typedef long long ll; ll n; ll a[17],d[17]; ll kangtuo() { bool bo[17];memset(bo,false,sizeof(bo)); ll sum=0; for(ll i=1;i<=n-1;i++) { ll k=0; for(ll j=i+1;j<=n;j++) //看第i個數字,它後面有多少數字比它小 if(a[i]>a[j])k++; sum=sum+k*d[n-i]; // cout<<i<<" "<<k<<endl; // bo[a[i]]=true; } sum++; return sum; } int main(){ scanf("%lld",&n); for(int i=1;i<=n;i++)scanf("%lld",&a[i]); d[0]=1;for(int i=1;i<=15;i++)d[i]=d[i-1]*i; printf("%lld\n",kangtuo()); return 0; }
二、康託展開的逆運算
我把之前的div改為/,div是pascal語言的運算子號,/是C++的,意思都一樣就是求兩個整數除法運算後的商(不理會餘數)
例3:1~5從小到大全排列中,找出第96個排列?
解:首先設x1x2x3x4x5, (x1等於?不知道),用96-1得到95,表示x1x2x3x4x5前面有95個序列。
第一個數x1,假設x1目前有k個比x1小的並且還沒有出現過的數,那麼
k:= 95 / (n-1)! = 95 / 24=3, 也就是有3個比x1小並且沒有出現過的數,那麼x1=4.
95變成95-3×24=23
第二個數x2,假設x2目前有k個比x2小的並且還沒有出現過的數,那麼
k:= 23 / (n-2)! = 23 / 6 = 3, 也就是有3個比x2小並且沒有出現過的數,那麼x2=5.(有3個數比它小的數是4,但4已經在之前出現過了,所以是5)
23變成 23 – 3 * 6 = 5
第三個數用5去除2! 得到2餘1,那第三個數就是還沒有出現過的第3小的數3(5變成1)
第四個數用1去除1! 得到1餘0,那第四個數就是還沒有出現過的第2小的數2
第五個數就是最後還沒有出現的那個。
所以這個數是45321
#include<cstdio> #include<cstring> using namespace std; typedef long long ll; ll n; ll a[17],d[17]; void _kangtuo(ll sum) { sum--; bool bo[17];memset(bo,true,sizeof(bo)); for(ll i=1;i<=n;i++) { ll k=sum/d[n-i]; sum-=k*d[n-i]; ll j; for(j=1;j<=n;j++) if(bo[j]==true) { if(k==0)break; k--; } bo[j]=false; a[i]=j; } for(ll i=1;i<n;i++)printf("%lld ",a[i]); printf("%lld\n",a[n]); } int main(){ scanf("%lld",&n); d[0]=1;for(int i=1;i<=15;i++)d[i]=d[i-1]*i; ll k;scanf("%lld",&k); _kangtuo(k); return 0; }