1. 程式人生 > 實用技巧 >康託展開和康託展開的逆運算

康託展開和康託展開的逆運算

八數碼問題不用康託展開判斷重複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個

2341:第10個
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不變。

第三個數是3,研究比3小的並且還沒有出現過的數為1個:2。那麼ans:=ans+ 1* (n-3)!,那麼ans:=18+1* 1=19
第四個數是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;
}